2009-04-08 4 views
162

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

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

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

-Update-
Так что, кажется, она не может быть решена путем ограничения каким-либо образом тогда. Из ответов есть три кандидатских решения:

  • Factory Pattern
  • Отражение
  • Активатор

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

+0

BTW: сегодня я бы, вероятно, решил это с помощью библиотеки IoC. –

ответ

238

Кроме простой пример:

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

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

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

+0

Поскольку этот конструктор находится на базовом слое (BaseFruit), я знаю, что у него будет конструктор. Но действительно, если в один прекрасный день я решаю, что базовый фрутт нуждается в большем количестве параметров, я мог бы быть ввернут. Однако рассмотрим класс ACtivator. Раньше об этом не слышал. –

+3

Это хорошо работает. Существует также процедура CreateInstance (), но у нее нет перегрузки для параметров для некоторого rason .. –

+8

Нет необходимости использовать 'new object [] {weight}'. 'CreateInstance' объявляется с помощью params, public public object CreateInstance (Type type, params object [] args)', поэтому вы можете просто сделать return (T) Activator.CreateInstance (typeof (T), weight); '. Если есть несколько параметров, передайте их в виде отдельных аргументов. Только если у вас уже есть сконфигурированные перечислимые параметры, если вы хотите преобразовать его в 'object []' и передать это в 'CreateInstance'. – ErikE

40

Да; изменить ваш где быть:

where T:BaseFruit, new() 

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

+42

Darn, я получил Robinsoned ... –

+3

@ Jon Skeet: Это очень близко, чтобы заставить меня смеяться громко (на работе!). –

+1

@MichaelMyers Я не был _that lucky_ – ppeterka

74

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

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

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

+1

Ты все фруктовый! :) –

+10

Я действительно просто хотел сказать, Skeeted. –

+1

По крайней мере, вы МОЖЕТЕ делать такие ограничения - Java всегда меня разочаровывает. –

13

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

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); 
} 

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

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

Хорошее предложение - хотя, если вы не будете осторожны, вы можете оказаться в аду API-интерфейса Java DOM с заводами :( –

+0

@Jon, не хотел бы этого :) – JaredPar

+0

Да, это решение, которое я рассматривал себя. Но я надеялся на что-то в линии ограничений. Угадай, что не тогда .. –

10

можно сделать с помощью отражения:

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: Добавлен конструктор == проверки нулевой.

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) }); 
} 
+0

Хотя мне не нравятся накладные расходы на отражение, как объяснили другие, это именно так, В данный момент. Видя, как этот конструктор не будет называться слишком много, я мог бы пойти с этим. Или завод. Еще не знаю. –

17

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

+0

Прямо к делу! ты получил мой голос –

0

Недавно я наткнулся на очень похожей проблемой. Просто хотел поделиться нашим решением со всеми вами. Я хотел я создал экземпляр 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) }); 
-2

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

// 
    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() для общественности не оказывает отрицательного влияния на неизменность. Пользователь этого класса может поместить другой объект, но это не изменит базовую коллекцию.