C# 中的动态构造函数



我正在尝试编写一个方法GetDynamicConstructor<T>该方法本质上将为给定类返回一个智能构造函数。它将接受字符串数组作为参数,并将它们解析为适当的类型(给定现有构造函数数据(。

public void Init()
{
DynamicConstructor<MyClass> ctor = GetDynamicConstructor<MyClass>();
MyClass instance = ctor(new string[] { "123", "abc" }); // parse "123" as int
}
public delegate T DynamicConstructor<T>(string[] args);
public DynamicConstructor<T> GetDynamicConstructor<T>()
{
ConstructorInfo originalCtor = typeof(T).GetConstructors().First();
ParameterInfo[] paramsInfo = originalCtor.GetParameters();
for (int i = 0; i < paramsInfo.Length; i++) {
Type paramType = paramsInfo[i].ParameterType;
// This is as far as I got :D
}
return null;
}
public class MyClass
{
int n;
string s;
public MyClass(int n, string s)
{
this.n = n;
this.s = s;
}
}

基本上,我想要的是用MyClass构造一个看起来像这样的方法。

public MyClass Example(string[] args)
{
return new MyClass(int.Parse(args[0]), args[1]);
}

这里只有基本类型,所以我可以指望我可能遇到的类型存在Parse

如何写GetDynamicConstructor<T>的正文?

此方法已存在于System.Activator类中:

public static object CreateInstance (Type type, params object[] args);

当然,必须存在与实际参数数据对应的构造函数重载。您可以使用Convert.ChangeType Method (Object, Type)更改参数的类型。

请参阅:learn.microsoft.com 上的 CreateInstance(Type, Object[](。

Activator.CreateInstance有 16 种不同的重载。

根据您想要如何使用它,有几种方法可以做到这一点。Steve16351 有一种方法是创建一个委托给一个在执行时执行所有反射的方法。另一种方法是在执行时生成一个看起来像您的 Example 方法的委托,然后将其缓存。不同之处在于前者可以更灵活,而后者会更快。在选择构造函数之前,对每次执行使用反射可以考虑哪些转换是成功的。虽然已编译的委托必须在参数可用之前知道要选择哪个构造函数,但它的性能特征更像是用 C# 本机编写的方法。下面是使用表达式树生成委托的实现。您需要为每种类型缓存此内容以获得最佳性能:

using System.Linq.Expressions;
public static DynamicConstructor<T> GetDynamicConstructor<T>()
{
ConstructorInfo originalCtor = typeof(T).GetConstructors().First();
var parameter = Expression.Parameter(typeof(string[]), "args");
var parameterExpressions = new List<Expression>();
ParameterInfo[] paramsInfo = originalCtor.GetParameters();
for (int i = 0; i < paramsInfo.Length; i++)
{
Type paramType = paramsInfo[i].ParameterType;
Expression paramValue = Expression.ArrayIndex(parameter, Expression.Constant(i));
if (paramType.IsEnum)
{
var enumParse = typeof(Enum).GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(Type), typeof(string) }, null);
var call = Expression.Call(null, enumParse, new[] { Expression.Constant(paramType), paramValue });
paramValue = Expression.Convert(call, paramType);
}
else if (paramType != typeof(string))
{
var parseMethod = paramType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
if (parseMethod == null)
{
throw new Exception($"Cannot find Parse method for type {paramType} (parameter index:{i})");
}
paramValue = Expression.Call(null, parseMethod, new[] { paramValue });
}            
parameterExpressions.Add(paramValue);
}
var newExp = Expression.New(originalCtor, parameterExpressions);
var lambda = Expression.Lambda<DynamicConstructor<T>>(newExp, parameter);
return lambda.Compile();
}

请注意,我添加了枚举的处理,因为 Parse 的调用方式不能与其他简单类型相同。

更新:

根据此处的注释,下面是一个扩展版本,用于发出将处理默认参数值的非泛型委托:

public static DynamicConstructor GetDynamicConstructor(Type type)
{
ConstructorInfo originalCtor = type.GetConstructors().First();
var parameter = Expression.Parameter(typeof(string[]), "args");
var parameterExpressions = new List<Expression>();
ParameterInfo[] paramsInfo = originalCtor.GetParameters();
for (int i = 0; i < paramsInfo.Length; i++)
{
Type paramType = paramsInfo[i].ParameterType;
// added check for default value on the parameter info.
Expression defaultValueExp;
if (paramsInfo[i].HasDefaultValue)
{
defaultValueExp = Expression.Constant(paramsInfo[i].DefaultValue);
}
else
{
// if there is no default value, then just provide 
// the type's default value, but we could potentially 
// do something else here
defaultValueExp = Expression.Default(paramType);
}
Expression paramValue;
paramValue = Expression.ArrayIndex(parameter, Expression.Constant(i));
if (paramType.IsEnum)
{
var enumParse = typeof(Enum).GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(Type), typeof(string) }, null);
var call = Expression.Call(null, enumParse, new[] { Expression.Constant(paramType), paramValue });
paramValue = Expression.Convert(call, paramType);
}
else if (paramType != typeof(string))
{
var parseMethod = paramType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
if (parseMethod == null)
{
throw new Exception($"Cannot find Parse method for type {paramType} (parameter index:{i})");
}
paramValue = Expression.Call(null, parseMethod, new[] { paramValue });
}
// here we bounds check the array and emit a conditional expression
// that will provide a default value if necessary. Equivalent to 
// something like i < args.Length ? int.Parse(args[i]) : default(int);  
// Of course if the parameter has a default value that is used instead, 
// and if the target type is different (long, boolean, etc) then
// we use a different parse method.
Expression boundsCheck = Expression.LessThan(Expression.Constant(i), Expression.ArrayLength(parameter));
paramValue = Expression.Condition(boundsCheck, paramValue, defaultValueExp);
parameterExpressions.Add(paramValue);
}
var newExp = Expression.New(originalCtor, parameterExpressions);
var lambda = Expression.Lambda<DynamicConstructor>(newExp, parameter);
return lambda.Compile();
}
}

最新更新