繁体   English   中英

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

[英]Create instance of generic type whose constructor requires a parameter?

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

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

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

-更新-
所以它似乎不能以任何方式通过约束来解决。 从答案中可以看出三个候选解决方案:

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

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

另外一个更简单的例子:

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

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

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

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

这是一种痛苦,但这就是生活:(

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

是的; 改变你的位置:

where T:BaseFruit, new()

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

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

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

您可以使用反射来完成:

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

作为 user1471935 建议的补充:

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

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

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

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

还有一个通用版本的 CreateInstance ( CreateInstance<T>() ),但该版本也不允许您提供构造函数参数。

我创建了这个方法:

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

您可以使用以下命令:

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

请务必查看以下参考资料

最近我遇到了一个非常相似的问题。 只是想与大家分享我们的解决方案。 我想从一个具有枚举的 json 对象创建一个Car<CarA>的实例:

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

如果您愿意使用 ac# 预编译器,您可以解决这个问题,以便它确实有编译时间限制:

 // 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 脚本调用预编译器,可选择在编译时重新生成源代码。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM