Создать экземпляр универсального типа, для конструктора которого требуется параметр?


230

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

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

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

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

  • Фабричный образец
  • отражение
  • возбудитель

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


1
Кстати, сегодня я бы, вероятно, решил это с помощью библиотеки IoC.
Борис Калленс

Отражение и Активатор на самом деле тесно связаны.
Роб Вермёлен

Ответы:


335

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

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

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

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


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

3
Этот работал отлично. Есть также процедура CreateInstance <T> (), но она не перегружена параметрами для некоторого rason ..
Борис Калленс

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

2
Это будет иметь проблемы с производительностью, которые я прочитал. Вместо этого используйте скомпилированную лямбду. vagifabilov.wordpress.com/2010/04/02/…
Дэвид

1
@RobVermeulen - я думаю, что-то вроде статического свойства в каждом классе Fruit, который содержит, Funcкоторый создает новый экземпляр. Предположим, Appleиспользование конструктора new Apple(wgt). Затем добавьте к Appleклассу это определение: static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);В Factory определите public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } Usage: Factory.CreateFruit(57.3f, Apple.CreateOne);-, который создает и возвращает Apple, с weight=57.3f.
ToolmakerSteve

92

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

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

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


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

@JonSkeet: Если я предоставлю API с помощью .NET generic для вызова в VB6.0 ... Работает ли он по-прежнему?
Рой Ли

@Roylee: я понятия не имею, но я подозреваю, что нет.
Джон Скит

Я думаю, что статические интерфейсы могут быть добавлены языковым компилятором без изменений во время выполнения, хотя было бы хорошо, если бы языковые команды координировали детали. Укажите, что каждый класс, претендующий на реализацию статического интерфейса, должен содержать вложенный класс с определенным именем, связанным с интерфейсом, который определяет статический одноэлементный экземпляр своего собственного типа. С интерфейсом будет связан статический универсальный тип с полем экземпляра, который должен быть загружен с помощью синглтона один раз через Reflection, но может использоваться сразу после этого.
суперкат

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

61

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

where T:BaseFruit, new()

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


Если конструктор не имеет параметров, это кажется мне безопасным.
PerpetualStudent

Ты спас мне жизнь. Мне не удалось ограничить T до class и new () ключевым словом.
Генотипек

28

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


1
Спасибо за предложение, оно привело меня туда, где я должен был быть. Хотя это не позволяет использовать параметризованный конструктор. Но вы можете использовать неуниверсальный вариант: Activator.CreateInstance (typeof (T), new object [] {...}), где массив объектов содержит аргументы для конструктора.
Роб Вермейлен

19

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

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 с огромным количеством фабрик :(
Jon Skeet

Да, это решение, которое я сам себе принимал. Но я надеялся на что-то в линии ограничений. Не думаю, тогда ..
Борис Калленс

@boris, к сожалению, искомого языка ограничений на данный момент не существует
JaredPar

11

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

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

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

В настоящее время это мой предпочтительный подход, потому что он не добавляет больше сложности на стороне вызова.
Роб Вермёлен

Но теперь я прочитал о предложении Activator, которое имеет такую ​​же гадость, как и вышеупомянутое решение для отражения, но с меньшим количеством строк кода :) Я собираюсь перейти к опции Activator.
Роб Вермейлен

1

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

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

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

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

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

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


1

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.