我正在尝试创建一种对象工厂来实例化一个实现特定接口的类型,其中实例化的类型在运行时之前是未知的,并且我试图在不使用反射的情况下实现这一点。我发现一些使用表达式可能可以实现这一点的例子,但我没有找到一个适用于我的案例的例子。可能是因为它根本不可能,但我想在这里提出问题以确保。
所以我到目前为止就是这样:
public static Func<Type, object[], IMyInterface> FactoryExpression =
Expression.Lambda<Func<Type, object[], IMyInterface>>(
/* Something that creates an instance of the type with given arguments */
).Compile()
public static IMyInterface GetTypeOfMyInterface()
{
Type t = Type.GetType(GetTypeNameFromSomewhere());
ConstructorInfo c = t.GetConstructors().First();
object[] args = ResolveCostructorArguments(c.GetParameters());
return FactoryExpression(t, args);
}
我对这些类型的表达式几乎没有任何表现。是否有可能让这个工作或我必须回到反思?
修改
通过使用Jon Hanna的例子,我想出了以下内容:
public class TypeInitializer<TResult>
{
private static readonly ConcurrentDictionary<string, Func<object[], TResult>> InstanceCreationMethods =
new ConcurrentDictionary<string, Func<object[], TResult>>();
public static TResult CreateInstance(ConstructorInfo constructorInfo, params object[] arguments)
{
ParameterInfo[] parameterInfo = constructorInfo.GetParameters();
IEnumerable<Type> parameterTypes = parameterInfo.Select(p => p.ParameterType);
string constructorSignatureKey = GetConstructorSignatureKey(constructorInfo.DeclaringType, parameterTypes);
Func<object[], TResult> factoryMethod = InstanceCreationMethods.GetOrAdd(constructorSignatureKey, key =>
{
Expression[] args = new Expression[parameterInfo.Length];
ParameterExpression param = Expression.Parameter(typeof(object[]));
for (int i = 0; i < parameterInfo.Length; i++)
args[i] = Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), parameterInfo[i].ParameterType);
return Expression
.Lambda<Func<object[], TResult>>(Expression.Convert(Expression.New(constructorInfo, args), typeof(TResult)), param)
.Compile();
});
return factoryMethod(arguments);
}
private static string GetConstructorSignatureKey(Type type, IEnumerable<Type> argumentTypes) => string.Concat(type.FullName, " (", string.Join(", ", argumentTypes.Select(at => at.FullName)), ")");
}
这看起来像预期的那样有效!为此非常感谢。 为了实验,我还使用Activator.CreateInstance以及constructorInfo.Invoke实现了一个实现,并构建了som性能测试以查看差异。
00:00:00.9246614, Actiator
00:00:00.7524483, Constructor Invoke
00:00:00.8235814, Compiled Expression
此测试定时使用每种方法创建100 000个相同类型的实例并打印结果。我有点沮丧地看到Constructor Invoke方法接缝效果更好!
答案 0 :(得分:2)
您可以使用Expression.New
创建实例。
您还需要将ConstructorInfo
传递给您的工厂,并使用Expression.Convert
将对象强制转换为界面。
public static Func<object[], IMyInterface> BuildFactoryExpression(ConstructorInfo ctor)
{
ParameterInfo[] par = ctor.GetParameters(); // Get the parameters of the constructor
Expression[] args = new Expression[par.Length];
ParameterExpression param = Expression.Parameter(typeof(object[])); // The object[] paramter to the Func
for (int i = 0; i != par.Length; ++i)
{
// get the item from the array in the parameter and cast it to the correct type for the constructor
args[i] = Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), par[i].ParameterType);
}
return Expression.Lambda<Func<object[], IMyInterface>>(
// call the constructor and cast to IMyInterface.
Expression.Convert(
Expression.New(ctor, args)
, typeof(IMyInterface)
), param
).Compile();
}
是的我用反射也解决了这个问题,这很简单。但在这种情况下,我希望尽可能避免性能损失。
这仍然是使用反射。如果您要反复使用相同的Func
和,那么您正在Compile()
编译为IL(例如非UWP)的上下文中运行,那么您可能会在这里获得,因为反射是曾经使用过一次来创建一个可重用的委托,从那时起就像你用C#方法编写委托一样。如果您可以输入您的参数而不是传递object[]
并且必须从中进行投射,您将获得更多。
如果您一次使用这些代表,那么您也可以使用反射。如果您只是处于解释表达式委托的上下文中,那么无论如何它都必须在内部使用反射。