конструктор как делегат-возможно ли это в 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 ответов


нет, среда 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; });

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

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

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).

на 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+


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

Метод Фабрики Шаблон


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


Марк 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);
}

почти то же самое, если ваш конструктор имеет params (в этом примере: 1 param типа 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 });
}

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