Подтвердить что ты не робот

С#: общие типы, у которых есть конструктор?

У меня есть следующий тестовый код С#:

  class MyItem
  {
    MyItem( int a ) {}
  }

  class MyContainer< T >
    where T : MyItem, new()
  {
    public void CreateItem()
    {
      T oItem = new T( 10 );
    }
  }

Visual Studio не может скомпилировать его, ошибка находится в строке, где используется "новое":

'T': cannot provide arguments when creating an instance of a variable type

Возможно ли в С# создать объект родового типа без конструктора без параметров? Это не проблема, чтобы сделать такую ​​вещь в С++-шаблонах, поэтому мне очень любопытно, почему я не могу сделать то же самое в С#. Может быть, какое-то дополнительное "where" требуется или синтаксис отличается?

4b9b3361

Ответ 1

Это можно сделать с отражением:

public void CreateItem()
{
  int constructorparm1 = 10;
  T oItem = Activator.CreateInstance(typeof(T), constructorparm1) as T;
}

Но не существует общего ограничения, чтобы гарантировать, что T реализует желаемый конструктор, поэтому я бы не советовал это делать, если вы не будете осторожны, чтобы объявить этот конструктор в каждом типе, реализующем интерфейс.

Ответ 2

С# и VB.Net в этом отношении не поддерживают понятие ограничения обобщения на наличие конструктора с определенными параметрами. Он поддерживает только ограничение наличия пустого конструктора.

Одна работа должна состоять в том, чтобы вызов вызывал в lambda factory, чтобы создать значение. Например,

public void CreateItem(Func<int,T> del) {
  T oItem = del(10);
}

Вызов сайта

CreateItem(x => new SomeClass(x));

Ответ 3

Нет такого общего ограничения, так что это невозможно сразу (это ограничение CLR). Если вы этого хотите, вы должны предоставить класс factory (который имеет конструктор без параметров) и передать его в качестве второго типичного параметра типа.

Ответ 4

IMO, наилучшим подходом здесь является метод инициализации, то есть

interface ISomeInterface {
    void Init(int i);
}
class Foo : ISomeInterface {
    void ISomeInterface.Init(int i) { /* ... */ }
}
static class Program {
    static T Create<T>(int i) where T : class, ISomeInterface, new() {
        T t = new T();
        t.Init(i);
        return t;
    }
    static void Main() {
        Foo foo = Create<Foo>(123);
    }
}

Однако вы можете делать то, что хотите, с помощью Expression (но без поддержки времени компиляции):

using System;
using System.Linq.Expressions;
class Foo {
    public Foo(int i) { /* ... */ }
}
static class Program {
    static T Create<T>(int i) {
        return CtorCache<T>.Create(i);
    }
    static class CtorCache<T> {
        static Func<int, T> ctor;
        public static T Create(int i) {
            if (ctor == null) ctor = CreateCtor();
            return ctor(i);
        }
        static Func<int, T> CreateCtor() {
            var param = Expression.Parameter(typeof(int), "i");
            var ci = typeof(T).GetConstructor(new[] {typeof(int)});
            if(ci == null) throw new InvalidOperationException("No such ctor");
            var body = Expression.New(ci, param);
            return Expression.Lambda<Func<int, T>>(body, param).Compile();
        }
    }
    static void Main() {
        Foo foo = Create<Foo>(123);
    }
}

Обратите внимание, что это кэширует и повторно использует делегат для производительности.

Ответ 5

Один шаблон, который я использую, заключается в том, чтобы связанный класс реализовал интерфейс, который определяет метод Init с соответствующей сигнатурой:

interface IMyItem
{
    void Init(int a);
}

class MyItem : IMyItem
{
    MyItem() {}
    void Init(int a) { }
}    

class MyContainer< T >
    where T : MyItem, IMyItem, new()
{
    public void CreateItem()
    {
        T oItem = new T();
        oItem.Init( 10 );
    }
}