Аргументы метода передаются через стек, т.е. по окончанию вызова конструктора о параметрах, переданных в него, ничего не известно. Чтобы узнать, какие аргументы были переданы в конструктор есть несколько способов.
Кэширование
Зачастую аргументы, передаваемые в конструктор, сохраняются в полях новосозданного объекта. С помощью рефлексии мы можем изъять эти поля. Лично я данный метод не рекомендую, т.к. вы должны знать внутреннее устройство типа, а т.к. эти поля, обычно, приватные, то метаданные о них от версии к версии могут меняться.
public sealed class Some
{
readonly int x;
public Some(int x)
{
this.x = x;
}
}
static void Main()
{
var some = new Some(5);
// проверки опущены
Console.WriteLine(some.GetField("x").GetValue(some)); // 5
}
Сохранить аргументы локально
Можно сохранить аргументы локально в стеке метода, вызывающего конструктор. Это самый простой и безопасный способ.
public sealed class Node
{
readonly Node? next;
public Node(Node? next = null)
{
this.next = next;
}
}
static void Main()
{
var next = new Node();
var node = new Node(next);
// `next` - это и есть аргумент 2-го конструктора
}
Прокси-класс
Если уж очень хочется сохранить аргументы глобально, при этом не вскрывая класс изнутри, то можно использовать прокси-класс.
public sealed class Some
{
readonly int a, b, c;
public Some(int a, int b, int c)
{
(this.a, this.b, this.c) = (a, b, c);
}
}
public sealed class SomeProxy
{
public Some Instance { get; }
public int PassedA { get; }
public int PassedB { get; }
public int PassedC { get; }
public SomeProxy(int a, int b, int c)
{
Instance = new Some(a, b, c);
(PassedA, PassedB, PassedC) = (a, b, c);
}
}
public interface ISomeOwner : IDisposable
{
Some Instance { get; }
void Remove();
}
public class SomeStorage
{
readonly LinkedList<SomeProxy> proxies;
⋮
public ISomeOwner Add(int a, int b, int c)
{
var proxy = new SomeProxy(a, b, c);
var node = proxies.Add(proxy);
⋮
return new SomeOwner(proxy.Instance, node);
}
⋮
seales class SomeOwner : ISomeOwner
{
internal SomeOwner(Some some, LinkedListNode<Some> node)
{
⋮
}
⋮
}
}
Важно! Это псевдо-код. Вам не следует его копировать в проекты для релиза.
После создания и добавления объекта типа Some в наше заранее созданное хранилище мы можем обращаться к аргументам, переданным в его конструктор, с помощью получения их из соответствующего SomeProxy.
В итоге вполне простыми способами мы можем реализовать хранение аргументов конструктора в самом классе, во внешних локальном и глобальном хранилищах. Способ выбирайте сами в зависимости от вашей ситуации.
Получить конструктор от объекта заренее известного типа довольно просто, но если вы хотите получить его от generic'а, то тут придётся постараться. Это я про 3-й способ — SomeProxy. Его можно заменить на вот такой класс:
public sealed class Proxy<out T>
{
public T Instance { get; }
public ConstructorInfo UsedConstructor { get; }
public IReadOnlyList<object?> PassedArgs { get; }
public Proxy(params object?[]? args)
{
if (!FindConstructor(out var ctor, args)) throw new ArgumentException("Suitable constructor not found", nameof(args));
UsedConstructor = ctor;
PassedArgs = new ReadOnlyCollection<object?>(args ?? Array.Empty<object?>);
Instance = UsedConstructor.Invoke(PassedArgs);
}
static bool FindConstructorByArgs([MaybeNullWhen(false)] out ConstructorInfo constructor, params object?[]? args)
{
constructor = null!;
var ctorArgTypes = args is null ? Array.Empty<Type?>() :
args.Select(a => a is null ? null : a.GetType());
var ctors = typeof(T).GetConstructors();
ConstructorInfo? ctor = null;
foreach (var _ctor in ctors)
{
var args = _ctor.GetParameters();
if (args.Length != ctorArgTypes.Length) continue;
for (var i = 0; i < args.Length; i++)
{
if (ctorArgTypes[i] is null &&
args[i].ParameterType != typeof(Nullable<>) &&
args[i].ParameterType.IsValueType)
continue;
if (args[i].ParameterType != ctorArgTypes[i])
continue;
}
ctor = _ctor;
break;
}
if (ctor is null) return false;
constructor = ctor;
return true;
}
}
Activator? – Exploding Kitten Nov 08 '20 at 09:32