简体   繁体   English

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

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

If BaseFruit has a constructor that accepts an int weight , can I instantiate a piece of fruit in a generic method like this?如果BaseFruit有一个接受int weight的构造函数,我可以用这样的通用方法实例化一块水果吗?

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

An example is added behind comments.在评论后面添加了一个示例。 It seems I can only do this if I give BaseFruit a parameterless constructor and then fill in everything through member variables.似乎只有给BaseFruit一个无参数的构造函数,然后通过成员变量填充所有内容,我才能做到这一点。 In my real code (not about fruit) this is rather impractical.在我的真实代码(不是关于水果)中,这是相当不切实际的。

-Update- -更新-
So it seems it can't be solved by constraints in any way then.所以它似乎不能以任何方式通过约束来解决。 From the answers there are three candidate solutions:从答案中可以看出三个候选解决方案:

  • Factory Pattern工厂模式
  • Reflection反射
  • Activator活化剂

I tend to think reflection is the least clean one, but I can't decide between the other two.我倾向于认为反射是最不干净的一种,但我无法在其他两种之间做出决定。

Additionally a simpler example:另外一个更简单的例子:

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

Note that using the new() constraint on T is only to make the compiler check for a public parameterless constructor at compile time, the actual code used to create the type is the Activator class.请注意,在 T 上使用 new() 约束只是为了让编译器在编译时检查公共无参数构造函数,用于创建类型的实际代码是 Activator 类。

You will need to ensure yourself regarding the specific constructor existing, and this kind of requirement may be a code smell (or rather something you should just try to avoid in the current version on c#).您需要确保自己了解现有的特定构造函数,并且这种要求可能是代码异味(或者更确切地说,您应该在当前的 c# 版本中尽量避免这种情况)。

You can't use any parameterised constructor.您不能使用任何参数化构造函数。 You can use a parameterless constructor if you have a " where T : new() " constraint.如果您有“ where T : new() ”约束,则可以使用无参数构造函数。

It's a pain, but such is life :(这是一种痛苦,但这就是生活:(

This is one of the things I'd like to address with "static interfaces" .这是我想用“静态接口”解决的问题之一 You'd then be able to constrain T to include static methods, operators and constructors, and then call them.然后您就可以约束 T 以包含静态方法、运算符和构造函数,然后调用它们。

Yes;是的; change your where to be:改变你的位置:

where T:BaseFruit, new()

However, this only works with parameterless constructors.但是,这只适用于无参数构造函数。 You'll have to have some other means of setting your property (setting the property itself or something similar).你必须有一些其他的方式来设置你的属性(设置属性本身或类似的东西)。

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

As Jon pointed out this is life for constraining a non-parameterless constructor.正如 Jon 指出的那样,这是约束非无参数构造函数的生命。 However a different solution is to use a factory pattern.然而,另一种解决方案是使用工厂模式。 This is easily constrainable这很容易限制

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

Yet another option is to use a functional approach.另一种选择是使用函数式方法。 Pass in a factory method.传入一个工厂方法。

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

You can do by using reflection:您可以使用反射来完成:

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

EDIT: Added constructor == null check.编辑:添加了构造函数 == null 检查。

EDIT: A faster variant using a cache:编辑:使用缓存的更快变体:

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

As an addition to user1471935's suggestion:作为 user1471935 建议的补充:

To instantiate a generic class by using a constructor with one or more parameters, you can now use the Activator class.要使用带有一个或多个参数的构造函数实例化泛型类,您现在可以使用 Activator 类。

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

The list of objects are the parameters you want to supply.对象列表是您要提供的参数。 According to Microsoft : 根据微软的说法

CreateInstance [...] creates an instance of the specified type using the constructor that best matches the specified parameters. CreateInstance [...] 使用与指定参数最匹配的构造函数创建指定类型的实例。

There's also a generic version of CreateInstance ( CreateInstance<T>() ) but that one also does not allow you to supply constructor parameters.还有一个通用版本的 CreateInstance ( CreateInstance<T>() ),但该版本也不允许您提供构造函数参数。

I created this method:我创建了这个方法:

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

I use that in this way:我以这种方式使用它:

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

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

Code:代码:

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

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

You can use the following command:您可以使用以下命令:

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

Be sure to see the following reference .请务必查看以下参考资料

Recently I came across a very similar problem.最近我遇到了一个非常相似的问题。 Just wanted to share our solution with you all.只是想与大家分享我们的解决方案。 I wanted to I created an instance of a Car<CarA> from a json object using which had an enum:我想从一个具有枚举的 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) });

If you are willing to use ac# precompiler, you could resolve this so that it does have compile time constraints:如果您愿意使用 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>();
...

Your precompiler would then turn the Fruit user source into:然后,您的预编译器会将 Fruit 用户源代码转换为:

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

Using Roslyn, your precompiler could look something like this (here is room for improvement):使用 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;
        }
    }

The Precompiler could be called using a T4 script, optionally regenerating the source at compile time.可以使用 T4 脚本调用预编译器,可选择在编译时重新生成源代码。

It is still possible, with high performance, by doing the following:通过执行以下操作,仍然可以实现高性能:

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

and

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

The relevant classes then have to derive from this interface and initialize accordingly.然后相关的类必须从这个接口派生并相应地初始化。 Please note, that in my case, this code is part of a surrounding class, which already has <T> as generic parameter.请注意,就我而言,此代码是周围类的一部分,该类已经将 <T> 作为泛型参数。 R, in my case, also is a read-only class. R,就我而言,也是一个只读类。 IMO, the public availability of Initialize() functions has no negative effect on the immutability. IMO,Initialize() 函数的公开可用性对不变性没有负面影响。 The user of this class could put another object in, but this would not modify the underlying collection.此类的用户可以放入另一个对象,但这不会修改底层集合。

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

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