创建其构造函数需要参数的泛型类型的实例?


229

如果BaseFruit有一个接受的构造函数,int weight我可以用这种通用方法实例化一块水果吗?

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

在注释后面添加了一个示例。似乎只有提供BaseFruit无参数构造函数,然后通过成员变量填写所有内容,我才能做到这一点。用我的真实代码(不是关于水果),这是不切实际的。

-更新-
因此,看来无论如何都无法通过约束来解决。从答案中可以找到三种候选解决方案:

  • 工厂模式
  • 反射
  • 活化剂

我倾向于认为反射是最不干净的一种,但是我无法在其他两种之间做出选择。


1
顺便说一句:今天,我可能会使用所选的IoC库解决此问题。
鲍里斯·卡伦斯

反射和激活器实际上是紧密相关的。
Rob Vermeulen

Answers:


335

另外一个简单的例子:

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

请注意,在T上使用new()约束只是使编译器在编译时检查公共的无参数构造函数,用于创建该类型的实际代码是Activator类。

您将需要确保自己与现有的特定构造函数有关,并且这种要求可能是代码气味(或者,您应该在c#的当前版本中尝试避免这种情况)。


由于此构造函数位于基类(BaseFruit)上,因此我知道它将具有一个构造函数。但是的确,如果有一天我决定需要添加更多的参数,那我可能会感到困惑。虽然会研究ACtivator类。以前没有听说过。
鲍里斯·卡伦斯

3
这个很好。还有一个的CreateInstance <T>()过程,但不具有对于一些罗先参数的过载..
鲍里斯Callens

20
无需使用new object[] { weight }CreateInstance是用params声明的public static object CreateInstance(Type type, params object[] args),所以您可以这样做return (T) Activator.CreateInstance(typeof(T), weight);。如果有多个参数,请将它们作为单独的参数传递。仅当您已经具有可构造的参数枚举时,才需要将其转换为并将其object[]传递给CreateInstance
ErikE

2
这将出现我已阅读的性能问题。改用编译的lambda。vagifabilov.wordpress.com/2010/04/02/…–
大卫(David)

1
@RobVermeulen-我认为每个Fruit类上都有一个静态属性,其中包含一个Func创建新实例的属性。假设Apple构造函数的用法是new Apple(wgt)。然后添加Apple类这样的定义:static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);在工厂定义public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } 用法: Factory.CreateFruit(57.3f, Apple.CreateOne);-创建并返回Apple,用weight=57.3f
制造商史蒂夫

92

您不能使用任何参数化的构造函数。如果有“ where T : new()”约束,则可以使用无参数构造函数。

这很痛苦,但是生活就是这样:(

这是我想通过“静态接口”解决的事情之一。然后,您可以限制T包含静态方法,运算符和构造函数,然后调用它们。


2
至少您可以做这样的约束-Java总是令我失望。
Marcel Jackwerth 2009年

@JonSkeet:如果我公开了在VB6.0中调用的带有.NET泛型的API,它仍然可以使用吗?
罗伊·李

@Roylee:我不知道,但我怀疑不是。
乔恩·斯基特

我认为语言编译器可以在不更改运行时的情况下添加静态接口,尽管让语言团队在细节上进行协调是一件好事。指定每个声称要实现静态接口的类都必须包含具有与接口相关的特定名称的嵌套类,该嵌套类定义了自己类型的静态单例实例。与该接口相关联的是带有实例字段的静态泛型类型,该实例字段需要通过反射加载一次单例,但此后可以直接使用。
超级猫2014年

参数化构造函数约束的处理方式几乎相同(使用工厂方法,并为其返回类型使用通用参数);在任何情况下都不会阻止使用不支持该功能的语言编写的代码声称在未定义适当的静态类型的情况下实现该接口,因此使用此类语言编写的代码在运行时可能会失败,但可以避免在用户中使用反射码。
超级猫2014年

61

是; 更改您的位置:

where T:BaseFruit, new()

但是,这仅适用于无参数构造函数。您将不得不通过其他方式设置属性(设置属性本身或类似属性)。


如果构造函数没有参数,这对我来说似乎很安全。
PerpetualStudent

你救了我的命。我无法将T限制为class和new()关键字。
Genotypek

28

最简单的解决方案 Activator.CreateInstance<T>()


1
谢谢你的建议,它使我到达了需要的地方。尽管这不允许您使用参数化的构造函数。但是,您可以使用非通用变量:Activator.CreateInstance(typeof(T),new object [] {...}),其中对象数组包含构造函数的参数。
Rob Vermeulen

19

正如Jon指出的那样,这是约束非参数化构造函数的生命。但是,另一种解决方案是使用工厂模式。这很容易约束

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
很好的建议-尽管如果您不小心的话,可能会遇到Java DOM API的麻烦,工厂大声疾呼:(
Jon Skeet

是的,这是我正在考虑的解决方案。但是我希望在约束范围内有所作为。猜猜不是
。.–鲍里斯·卡伦斯

@boris,很遗憾,您正在寻找的约束语言在此时间点不存在
JaredPar 2009年

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

尽管我不喜欢反射的开销,但正如其他人所解释的那样,这只是当前的方式。看到这个构造函数不会被太多调用,我可以这样做。还是工厂。还不知道
鲍里斯·卡伦斯

目前,这是我的首选方法,因为它不会在调用方增加更多的复杂性。
Rob Vermeulen

但是,现在我已经阅读了有关Activator建议的内容,该建议与上述反射解决方案具有类似的缺点,但是使用的代码较少:)我将选择Activator选项。
Rob Vermeulen

1

作为对user1471935的建议的补充:

若要通过使用带有一个或多个参数的构造函数实例化泛型类,现在可以使用Activator类。

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

对象列表是您要提供的参数。根据微软的说法

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也是一个只读类。IMO,Initialize()函数的公共可用性对不变性没有负面影响。此类的用户可以放入另一个对象,但这不会修改基础集合。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.