如果BaseFruit
有一个接受int weight
的构造函数,我可以在这样的泛型方法中实例化一块水果吗?
public void AddFruit<T>()where T: BaseFruit{
BaseFruit fruit = new T(weight); /*new Apple(150);*/
fruit.Enlist(fruitManager);
}
在评论后面添加了一个示例。如果我给BaseFruit
一个无参数构造函数然后通过成员变量填充所有内容,我似乎只能这样做。在我的真实代码(不是关于水果)中,这是不切实际的。
-Update -
所以它似乎无法以任何方式通过约束来解决。从答案中有三个候选解决方案:
我倾向于认为反思是最不干净的,但我不能在其他两个之间做出决定。
答案 0 :(得分:289)
另外一个更简单的例子:
return (T)Activator.CreateInstance(typeof(T), new object[] { weight });
请注意,在T上使用new()约束只是为了让编译器在编译时检查公共无参数构造函数,用于创建类型的实际代码是Activator类。
您需要确保自己对于现有的特定构造函数,并且这种要求可能是代码异味(或者更确切地说,您应该在c#的当前版本中尽量避免这种情况。)
答案 1 :(得分:84)
您不能使用任何参数化构造函数。如果您有“where T : new()
”约束,则可以使用无参数构造函数。
这是一种痛苦,但这就是生活:(
这是我想用"static interfaces"解决的问题之一。然后,您可以将T约束为包含静态方法,运算符和构造函数,然后调用它们。
答案 2 :(得分:49)
是;改变你的位置:
where T:BaseFruit, new()
但是,这仅适用于无参数构造函数。您必须有其他一些设置属性的方法(设置属性本身或类似的东西)。
答案 3 :(得分:22)
最简单的解决方案
Activator.CreateInstance<T>()
答案 4 :(得分:16)
正如乔恩所指出的,这是限制非参数构造函数的生命。然而,不同的解决方案是使用工厂模式。这很容易受到限制
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);
}
答案 5 :(得分: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);
}
编辑:添加了构造函数== null check。
编辑:使用缓存的更快变体:
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) });
}
答案 6 :(得分: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);
答案 7 :(得分: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) });
答案 8 :(得分:0)
作为对user1471935的建议的补充:
要使用带有一个或多个参数的构造函数实例化泛型类,现在可以使用Activator类。
T instance = Activator.CreateInstance(typeof(T), new object[] {...})
对象列表是您要提供的参数。 According to Microsoft:
CreateInstance [...]使用与指定参数最匹配的构造函数创建指定类型的实例。
还有一个通用版本的CreateInstance(CreateInstance<T>()
),但该版本也不允许您提供构造函数参数。
答案 9 :(得分:0)
您可以使用以下命令:
T instance = (T)typeof(T).GetConstructor(new Type[0]).Invoke(new object[0]);
请务必查看以下内容 reference。
答案 10 :(得分:0)
如果您愿意使用 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 脚本调用预编译器,可选择在编译时重新生成源代码。
答案 11 :(得分:-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 );
}
然后,相关类必须从此接口派生并相应地进行初始化。 请注意,在我的情况下,此代码是周围类的一部分,该类已经具有&lt; T&gt;作为通用参数。 在我的例子中,R也是一个只读类。 IMO,Initialize()函数的公共可用性对不变性没有负面影响。此类的用户可以放入另一个对象,但这不会修改底层集合。