Создать экземпляр родового типа?

Если BaseFruit имеет конструктор, который принимает int weight, могу ли я создать экземпляр плода в общем методе, подобном этому?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

Пример добавляется за комментариями. Кажется, я могу сделать это только в том случае, если я дам BaseFruit конструктор без параметров и затем заполнить все через переменные-члены. В моем реальном коде (не о фруктах) это довольно непрактично.

-Обновление -
Похоже, что в любом случае это невозможно решить ограничениями. Из ответов есть три варианта решения:

  • Factory Шаблон
  • Отражение
  • Активатор

Я склонен думать, что отражение является наименее чистым, но я не могу решить между двумя другими.

Ответ 1

Кроме того, более простой пример:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

Обратите внимание, что использование ограничения new() на T заключается только в том, чтобы проверить компилятор для открытого конструктора без параметров во время компиляции, фактический код, используемый для создания типа, является классом Activator.

Вам нужно будет убедиться в том, что существует конкретный конструктор, и это требование может быть запахом кода (или, скорее, что-то, что вы должны просто попытаться избежать в текущей версии на С#).

Ответ 2

Вы не можете использовать какой-либо параметризованный конструктор. Вы можете использовать конструктор без параметров, если у вас есть ограничение "where T : new()".

Это боль, но такова жизнь: (

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

Ответ 3

Да; измените, где вы находитесь:

where T:BaseFruit, new()

Однако это работает только с конструкторами без параметров. Вам нужно будет иметь другие способы настройки вашей собственности (установка самого свойства или что-то подобное).

Ответ 4

Самое простое решение Activator.CreateInstance<T>()

Ответ 5

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

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

Еще один вариант - использовать функциональный подход. Перейдите в метод factory.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}

Ответ 6

Вы можете сделать, используя отражение:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

EDIT: Добавлен конструктор == null check.

EDIT: Более быстрый вариант с использованием кеша:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}

Ответ 7

Недавно я столкнулся с очень похожей проблемой. Просто хотел поделиться нашим решением со всеми вами. Я хотел создать экземпляр Car<CarA> из объекта json с использованием перечисления:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });

Ответ 8

В дополнение к предложению пользователя 1471935:

Чтобы создать экземпляр универсального класса с помощью конструктора с одним или несколькими параметрами, теперь вы можете использовать класс Activator.

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

Список объектов - это параметры, которые вы хотите указать. По словам Microsoft:

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

Существует также универсальная версия CreateInstance (CreateInstance<T>()), но она также не позволяет указывать параметры конструктора.

Ответ 9

По-прежнему возможно, с высокой производительностью, выполняя следующие действия:

    //
    public List<R> GetAllItems<R>() where R : IBaseRO, new() {
        var list = new List<R>();
        using ( var wl = new ReaderLock<T>( this ) ) {
            foreach ( var bo in this.items ) {
                T t = bo.Value.Data as T;
                R r = new R();
                r.Initialize( t );
                list.Add( r );
            }
        }
        return list;
    }

и

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

Соответствующие классы затем должны извлекаться из этого интерфейса и соответственно инициализировать. Обратите внимание, что в моем случае этот код является частью окружающего класса, который уже имеет <T> как общий параметр. R, в моем случае, также является классом только для чтения. ИМО, доступность функций Initialize() для общественности не оказывает отрицательного влияния на неизменность. Пользователь этого класса может включить другой объект, но это не изменит базовую коллекцию.