Создать экземпляр универсального типа? - PullRequest
198 голосов
/ 08 апреля 2009

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

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

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

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

  • Заводская модель
  • Отражение
  • Активатор

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

Ответы [ 10 ]

291 голосов
/ 09 апреля 2009

Дополнительно более простой пример:

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

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

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

84 голосов
/ 08 апреля 2009

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

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

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

49 голосов
/ 08 апреля 2009

Да; измените свое местоположение:

where T:BaseFruit, new()

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

22 голосов
/ 14 марта 2013

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

16 голосов
/ 08 апреля 2009

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

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);
}
11 голосов
/ 08 апреля 2009

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

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

РЕДАКТИРОВАТЬ: Добавлен конструктор == проверка нуля.

РЕДАКТИРОВАТЬ: Более быстрый вариант с использованием кэша:

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) });
}
1 голос
/ 08 января 2019

Я создал этот метод:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

Я использую это таким образом:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

Код:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);
0 голосов
/ 11 декабря 2018

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

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

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

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

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

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

0 голосов
/ 28 января 2017

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

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

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

0 голосов
/ 25 мая 2016

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