конструктор как делегат-возможно ли это в C#?


у меня есть класс, как показано ниже:

class Foo
{
  public Foo(int x) { ... }
}

и мне нужно передать в определенный метод делегат вроде этого:

delegate Foo FooGenerator(int x);

можно ли передать конструктор непосредственно как FooGenerator значение, без необходимости вводить:

delegate(int x) { return new Foo(x); }

?

EDIT: для моего личного использования вопрос относится к .NET 2.0, но подсказки/ответы для 3.0+ также приветствуются.

7 52

7 ответов:

нет, среда CLR не позволяет привязывать делегаты к ConstructorInfo.

вы можете просто создать свой собственный:
static T Make<T>(Action<T> init) where T : new()
{
  var t = new T();
  init(t);
  return t;
}

использование

var t = Make<Foo>( x => { x.Bar = "bar"; x.Baz = 1; });

Я предполагаю, что вы обычно делаете что-то подобное в рамках реализации фабрики, где фактические типы не известны во время компиляции...

во-первых, обратите внимание, что более простой подход может быть шагом инициализации после создания, тогда вы можете использовать дженерики:

static T Create<T>({args}) where T : class, ISomeInitInterface, new() {
    T t = new T();
    t.Init(args);
    return t;
}

вы можете использовать MakeGenericMethod и/или CreateDelegate.


в противном случае; вы можете сделать это на лету с Expression (3.5) или DynamicMethod (2.0).

The Expression подход проще кодировать:

    var param = Expression.Parameter(typeof(int), "val");
    var ctor = typeof(Foo).GetConstructor(new[] { typeof(int) });
    var lambda = Expression.Lambda<Func<int, Foo>>(
        Expression.New(ctor, param), param);
    var func = lambda.Compile();
    Foo foo = func(123);
    string s = foo.ToString(); // proof

или (используя DynamicMethod):

    ConstructorInfo ctor = typeof(Foo).GetConstructor(new[] { typeof(int) });
    DynamicMethod dm = new DynamicMethod("Create", typeof(Foo),
            new Type[] { typeof(int) }, typeof(Foo), true);
    ILGenerator il = dm.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Newobj, ctor);
    il.Emit(OpCodes.Ret);
    Converter<int, Foo> func = (Converter<int, Foo>)
        dm.CreateDelegate(typeof(Converter<int, Foo>));        
    Foo foo = func(123);
    string s = foo.ToString(); // proof

Я думаю, что так же лаконично, как вы собираетесь получить (не переходя к заводскому шаблону) , будет что-то с анонимными методами, например:

delegate Foo FooGenerator(int x);

...    

void DoStuff()
{
    YourDelegateConsumer(x => new Foo(x));
}

Это не делает строго то, что вы просили (так как вы передаете делегат анонимному методу, который возвращает новый экземпляр, а не прямой делегат конструктору), но я не думаю, что то, что вы просите, строго возможно.

это, конечно, при условии, что вы используете 3.5+

похоже, что вы, вероятно, хотите использовать шаблон фабрики классов.

Заводской Метод Pattern

к сожалению, нет, конструкторы не совсем то же самое, что методы, и поэтому вы не можете создать делегат, который указывает на них. Это интересная идея, хотя, возможно, с большей информацией мы могли бы разработать какой-то обходной путь, который был бы синтаксически похож.

Марк Gravell вдохновил меня на следующее Очень простое решение:

static void Main()
{
    Pet a = _MakeObject(typeof(Dog));
    Pet b = _MakeObject(typeof(Cat));
}

private static Pet _MakeObject(Type type)
{
    ConstructorInfo info = type.GetConstructor(new Type[0]);
    return (Pet)info?.Invoke(null);
}

почти то же самое, если конструктор имеет параметры (в данном примере: 1 параметр типа int):

static void Main()
{
    Pet a = _MakeObject(typeof(Dog), 5);
    Pet b = _MakeObject(typeof(Cat), 7);
}

private static Pet _MakeObject(Type type, int age)
{
    ConstructorInfo info = type.GetConstructor(new [] { typeof(int) });
    return (Pet)info?.Invoke(new object[] { age });
}

Я предполагаю, что это невозможно, так как вы передадите метод объекта, который еще не был создан.