【问题标题】:Create instance of generic type whose constructor requires a parameter?创建其构造函数需要参数的泛型类型的实例?
【发布时间】:2009-04-08 19:26:06
【问题描述】:

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

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

在cmets后面加了一个例子。看来我只有给BaseFruit一个无参数的构造函数,然后通过成员变量填写所有内容才能做到这一点。在我的真实代码中(不是关于水果),这是相当不切实际的。

-更新-
因此,它似乎无法以任何方式通过约束来解决。从答案中有三个候选解决方案:

  • 工厂模式
  • 反射
  • 激活器

我倾向于认为反射是最不干净的,但我无法在其他两个之间做出决定。

【问题讨论】:

  • 顺便说一句:今天我可能会用选择的 IoC 库来解决这个问题。
  • Reflection 和 Activator 其实是密切相关的。

标签: c# .net generics


【解决方案1】:

另外一个更简单的例子:

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

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

您需要确保自己了解现有的特定构造函数,而这种要求可能是代码异味(或者更确切地说,您应该在 c# 的当前版本中尽量避免这种情况)。

【讨论】:

  • 因为这个构造函数在基类(BaseFruit)上,我知道它会有一个构造函数。但事实上,如果有一天我决定 basefruit 需要更多参数,我可能会被搞砸。不过会研究 ACtivator 类。以前没听说过。
  • 这个效果很好。还有一个 CreateInstance() 过程,但是对于某些 rason 的参数没有重载..
  • 没有必要使用new object[] { weight }CreateInstance 是用参数声明的,public static object CreateInstance(Type type, params object[] args),所以你可以做return (T) Activator.CreateInstance(typeof(T), weight);。如果有多个参数,请将它们作为单独的参数传入。只有当您已经构建了一个可枚举的参数时,您才应该费心将其转换为 object[] 并将其传递给 CreateInstance
  • 这会有我读过的性能问题。请改用已编译的 lambda。 vagifabilov.wordpress.com/2010/04/02/…
  • @RobVermeulen - 我认为类似于每个 Fruit 类的静态属性,它包含一个创建新实例的 Func。假设Apple 构造函数用法为new Apple(wgt)。然后将这个定义添加到Apple 类中:static Func&lt;float, Fruit&gt; CreateOne { get; } = (wgt) =&gt; new Apple(wgt); 在工厂中定义public static Fruit CreateFruitGiven(float weight, Func&lt;float, Fruit&gt; createOne) { return createOne(weight); } 用法:Factory.CreateFruit(57.3f, Apple.CreateOne);——创建并返回一个Apple,带有weight=57.3f
【解决方案2】:

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

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

这是我想通过"static interfaces" 解决的问题之一。然后,您可以约束 T 以包含静态方法、运算符和构造函数,然后调用它们。

【讨论】:

  • 至少你可以做这样的限制——Java总是让我失望。
  • @JonSkeet:如果我使用 .NET 泛型公开 API 以在 VB6.0 中调用......它仍然可行吗?
  • @Roylee:我不知道,但我怀疑不是。
  • 我认为静态接口可以由语言编译器添加,而无需更改运行时,但最好让语言团队在细节上进行协调。指定每个声称实现静态接口的类必须包含一个具有特定接口相关名称的嵌套类,该类定义了它自己类型的静态单例实例。与接口相关联的是一个带有实例字段的静态泛型类型,该实例字段需要通过反射与单例一起加载一次,但之后可以直接使用。
  • 参数化构造函数约束的处理方式大致相同(使用工厂方法,并为其返回类型使用泛型参数);在这两种情况下,都不会阻止用不支持此类功能的语言编写的代码声称在没有定义正确的静态类型的情况下实现接口,因此使用此类语言编写的代码可能会在运行时失败,但可以在用户中避免反射代码。
【解决方案3】:

是的;改变你的位置:

where T:BaseFruit, new()

但是,这只适用于 无参数 构造函数。你必须有一些其他的方法来设置你的属性(设置属性本身或类似的东西)。

【讨论】:

  • 如果构造函数没有参数,这对我来说似乎是安全的。
  • 你救了我的命。我无法将 T 限制为 class 和 new() 关键字。
【解决方案4】:

最简单的解决方案 Activator.CreateInstance&lt;T&gt;()

【讨论】:

  • 感谢您的建议,它把我带到了我需要的地方。尽管这不允许您使用参数化构造函数。但是您可以使用非泛型变体: Activator.CreateInstance(typeof(T), new object[] {...}) 其中对象数组包含构造函数的参数。
【解决方案5】:

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

【讨论】:

  • 好建议 - 虽然如果你不小心,你可能会陷入 Java DOM API 的地狱,工厂众多:(
  • 是的,这是我自己考虑的解决方案。但我希望在限制范围内有所作为。那就别猜了..
  • @boris,很遗憾,您正在寻找的约束语言目前不存在
【解决方案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);
}

编辑:添加了构造函数 == null 检查。

编辑:使用缓存的更快变体:

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 选项。跨度>
【解决方案7】:

作为对user1471935建议的补充:

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

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

对象列表是您要提供的参数。 According to Microsoft:

CreateInstance [...] 使用与指定参数最匹配的构造函数创建指定类型的实例。

还有一个通用版本的 CreateInstance (CreateInstance&lt;T&gt;()),但它也不允许您提供构造函数参数。

【讨论】:

    【解决方案8】:

    我创建了这个方法:

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

    【讨论】:

      【解决方案9】:

      您可以使用以下命令:

       T instance = (T)typeof(T).GetConstructor(new Type[0]).Invoke(new object[0]);
      

      请务必查看以下内容 reference.

      【讨论】:

      【解决方案10】:

      最近我遇到了一个非常相似的问题。只是想与大家分享我们的解决方案。我想从一个 json 对象创建一个 Car&lt;CarA&gt; 的实例,使用它有一个枚举:

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

      【讨论】:

        【解决方案11】:

        如果您愿意使用 c# 预编译器,您可以解决此问题,使其具有编译时间限制:

         // Used attribute
         [AttributeUsage(AttributeTargets.Parameter)]
         class ResolvedAsAttribute : Attribute
         {
            public string Expression;
            public ResolvedAsAttribute(string expression)
            {
                this.Expression = expression;
            }
         }
        
        // Fruit manager source:
        class FruitManager {
        
            ...
        
            public void AddFruit<TFruit>([ResolvedAs("(int p) => new TFruit(p)")] Func<int,TFruit> ctor = null)where TFruit: BaseFruit{
                BaseFruit fruit = ctor(weight); /*new Apple(150);*/
                fruit.Enlist(fruitManager);
            }
        }
        
        // Fruit user source:
        #ResolveInclude ../Managers/FruitManager.cs
        ...
        fruitManager.AddFruit<Apple>();
        ...
        

        然后您的预编译器会将 Fruit 用户源代码转换为:

        ...
        fruitManager.AddFruit<Apple>((int p) => new Apple(p));
        ...
        

        使用 Roslyn,您的预编译器可能看起来像这样(这里有改进的余地):

            using System;
            using System.Collections.Generic;
            using System.IO;
            using System.Linq;
            using System.Text;
            using Microsoft.CodeAnalysis;
            using Microsoft.CodeAnalysis.CSharp;
            using Microsoft.CodeAnalysis.CSharp.Syntax;
            using Microsoft.CodeAnalysis.CSharp.Symbols;
            using System.Threading;
            using System.Text.RegularExpressions;
        
            public class CsResolveIncludeAnalyser : CSharpSyntaxWalker
            {
                private List<(string key, MethodDeclarationSyntax node)> methodsToResolve = new List<(string key, MethodDeclarationSyntax node)>();
                public List<(string key, MethodDeclarationSyntax node)> Analyse(string source)
                {
                    var tree = CSharpSyntaxTree.ParseText(source);
                    var syntaxRoot = tree.GetRoot();
                    Visit(tree.GetRoot());
                    return methodsToResolve;
                }
        
                public override void VisitMethodDeclaration(MethodDeclarationSyntax methodDeclaration)
                {
                    base.VisitMethodDeclaration(methodDeclaration);
        
                    if (methodDeclaration.ParameterList.Parameters.Count > 0)
                    {
                        foreach (var parm in methodDeclaration.ParameterList.Parameters)
                        {
                            var parmHasResolvedAs = parm.AttributeLists.Where((el) => el.Attributes.Where((attr) => attr.Name is IdentifierNameSyntax && ((IdentifierNameSyntax)attr.Name).Identifier.Text.Contains("ResolvedAs")).Any()).Any();
                            if (parmHasResolvedAs)
                            {
                                var name = methodDeclaration.Identifier.ValueText;
                                methodsToResolve.Add((name, methodDeclaration));
                                return;
                            }
                        }
                    }
                }
            }
        
        
            public class CsSwiftRewriter : CSharpSyntaxRewriter
            {
                private string currentFileName;
                private bool withWin32ErrorHandling;
                private Dictionary<string,MethodDeclarationSyntax> methodsToResolve = new Dictionary<string, MethodDeclarationSyntax>();
        
                private Dictionary<string, MethodDeclarationSyntax> getMethodsToResolve(string source, string fileName)
                {
                    Dictionary<string, MethodDeclarationSyntax> methodsToResolve = new Dictionary<string, MethodDeclarationSyntax>();
        
                    var path = Path.GetDirectoryName(fileName);
                    var lines = source.Split(new[] { '\r', '\n' });
                    var resolveIncludes = (from el in lines where el.StartsWith("#ResolveInclude") select el.Substring("#ResolveInclude".Length).Trim()).ToList();
        
                    var analyser = new CsResolveIncludeAnalyser();
                    foreach (var resolveInclude in resolveIncludes)
                    {
                        var src = File.ReadAllText(path + "/" + resolveInclude);
                        var list = analyser.Analyse(src);
                        foreach (var el in list)
                        {
                            methodsToResolve.Add(el.key, el.node);
                        }
                    }
        
                    return methodsToResolve;
                }
                public static string Convert(string source, string fileName)
                {
                    return Convert(source, fileName, false);
                }
        
                public static string Convert(string source, string fileName, bool isWithWin32ErrorHandling)
                {
        
                    var rewriter = new CsSwiftRewriter() { currentFileName = fileName, withWin32ErrorHandling = isWithWin32ErrorHandling };
                    rewriter.methodsToResolve = rewriter.getMethodsToResolve(source, fileName);
        
                    var resolveIncludeRegex = new Regex(@"(\#ResolveInclude)\b");
                    source = resolveIncludeRegex.Replace(source, "//$1");
        
                    var tree = CSharpSyntaxTree.ParseText(source);
                    var syntaxRoot = tree.GetRoot();
                    var result = rewriter.Visit(tree.GetRoot());
                    return "#line 1 \"" + Path.GetFileName(fileName) + "\"\r\n" + result.ToFullString();
                }
        
        
                internal List<string> transformGenericArguments(List<string> arguments, GenericNameSyntax gName, TypeParameterListSyntax typeParameterList)
                {
                    var res = new List<string>();
                    var typeParameters = typeParameterList.ChildNodes().ToList();
        
                    foreach (var argument in arguments)
                    {
                        var arg = argument;
                        for (int i = 0; i < gName.TypeArgumentList.Arguments.Count; i++)
                        {
                            var key = typeParameters[i];
                            var replacement = gName.TypeArgumentList.Arguments[i].ToString();
                            var regex = new System.Text.RegularExpressions.Regex($@"\b{key}\b");
                            arg = regex.Replace(arg, replacement);
                        }
                        res.Add(arg);
                    }
        
                    return res;
                }
        
                const string prefix = "";
                internal List<string> extractExtraArguments(MethodDeclarationSyntax methodDeclaration)
                {
                    var res = new List<String>();
        
                    foreach (var parm in methodDeclaration.ParameterList.Parameters)
                    {
                        foreach (var attrList in parm.AttributeLists)
                        {
                            foreach (var attr in attrList.Attributes)
                            {
                                if (attr.Name is IdentifierNameSyntax && string.Compare(((IdentifierNameSyntax)attr.Name).Identifier.Text, "ResolvedAs") == 0)
                                {
                                    var programmCode = attr.ArgumentList.Arguments.First().ToString().Trim();
                                    var trimmedProgrammCode = (programmCode.Length >= 2 && programmCode[0] == '"' && programmCode[programmCode.Length - 1] == '"') ? programmCode.Substring(1, programmCode.Length - 2) : programmCode;
                                    res.Add(prefix + parm.Identifier.Text + ":" + trimmedProgrammCode);
                                }
                            }
                        }
                    }
                    return res;
                }
        
                internal List<string> extractExtraArguments(MethodDeclarationSyntax methodDeclaration, SimpleNameSyntax name)
                {
                    var arguments = extractExtraArguments(methodDeclaration);
                    if (name != null && name is GenericNameSyntax)
                    {
                        var gName = name as GenericNameSyntax;
                        return transformGenericArguments(arguments, gName, methodDeclaration.TypeParameterList);
                    }
        
                    return arguments;
                }
        
                public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax c_expressionStatement)
                {
                    InvocationExpressionSyntax expressionStatement = (InvocationExpressionSyntax) base.VisitInvocationExpression(c_expressionStatement);
        
                    List<string> addedArguments = null;
                    switch (expressionStatement.Expression)
                    {
                        case MemberAccessExpressionSyntax exp:
                            if (methodsToResolve.ContainsKey(exp.Name?.Identifier.ValueText))
                            {
                                addedArguments = extractExtraArguments(methodsToResolve[exp.Name.Identifier.ValueText], exp.Name);
                            }
                            break;
                        case GenericNameSyntax gName:
                            if (methodsToResolve.ContainsKey(gName.Identifier.ValueText))
                            {
                                addedArguments = extractExtraArguments(methodsToResolve[gName.Identifier.ValueText], gName);
                            }
                            break;
                        default:
                            var name = (from el in expressionStatement.ChildNodes()
                                        where el is GenericNameSyntax
                                        select (el as GenericNameSyntax)).FirstOrDefault();
                            if (name != default(GenericNameSyntax))
                            {
                                if (methodsToResolve.ContainsKey(name.Identifier.ValueText))
                                {
                                    addedArguments = extractExtraArguments(methodsToResolve[name.Identifier.ValueText], name);
                                }
                            }
                            break;
                    }
        
                    if (addedArguments?.Count > 0)
                    {
                        var addedArgumentsString = string.Join(",", addedArguments);
                        var args = expressionStatement.ArgumentList.ToFullString();
                        var paras = $"({(expressionStatement.ArgumentList.Arguments.Count > 0 ? string.Join(",", args.Substring(1,args.Length - 2), addedArgumentsString) : addedArgumentsString)})" ;
                        var argList = SyntaxFactory.ParseArgumentList(paras);
                        return expressionStatement.WithArgumentList(argList);
                    }
        
                    return expressionStatement;
                }
            }
        

        可以使用 T4 脚本调用预编译器,可选择在编译时重新生成源代码。

        【讨论】:

          【解决方案12】:

          通过执行以下操作,仍然可以实现高性能:

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

          【讨论】:

            猜你喜欢
            • 2017-10-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多