[英]C# Expression to dynamically instantiate type without reflection
我試圖創建一種對象工廠來實例化實現特定接口的類型,其中在運行時之前不知道要實例化的類型,並且我試圖在不使用反射的情況下實現這一點。 我發現一些示例可能會暗示使用Expressions,但是我沒有設法找到適合我的情況的示例。 可能是根本不可能,但我想在這里提出這個問題以確保。
所以我到目前為止是這樣的:
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);
}
我對這些類型的表達式沒有任何經驗。 是否有可能讓它正常工作,還是我不得不退一步思考?
編輯:
通過使用喬恩·漢娜的示例,我得出以下結論:
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個相同類型的實例,並打印了結果。 令我驚訝的是,構造函數調用方法的接縫性能更好!
您可以使用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[]
並從中強制轉換,那么您將獲得更多。
如果您一次使用這些委托,那么最好只使用反射。 如果僅在解釋表達式的委托的上下文中,則無論如何都必須在內部使用反射。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.