[英]How to create a Expression.Lambda when a type is not known until runtime?
最好使用代碼解釋。 我有一個泛型類,它有一個返回整數的方法。 這是一個簡單的版本,用於解釋......
public class Gen<T>
{
public int DoSomething(T instance)
{
// Real code does something more interesting!
return 1;
}
}
在運行時,我使用反射來發現某事物的類型,然后想要為該特定類型創建我的Gen類的實例。 這很容易,像這樣完成......
Type fieldType = // This is the type I have discovered
Type genericType = typeof(Gen<>).MakeGenericType(fieldType);
object genericInstance = Activator.CreateInstance(genericType);
我現在想要創建一個Expression,它將參數作為泛型類型的一個實例,然后調用該類型的DoSomething方法。 所以我希望Expression能有效地執行此操作......
int answer = genericInstance.DoSomething(instance);
...除了我之前在運行時某點之后沒有'實例',並且genericInstance是生成的類型,如上所示。 我為此創建Lambda的嘗試如下......
MethodInfo mi = genericType.GetMethod("DoSomething",
BindingFlags.Instance | BindingFlags.Public);
var p1 = Expression.Parameter(genericType, "generic");
var p2 = Expression.Parameter(fieldType, "instance");
var x = Expression.Lambda<Func<genericType, fieldType, int>>
(Expression.Call(p1, mi, p2),
new[] { p1, p2 }).Compile();
......所以以后我可以用這樣的東西來稱它...
int answer = x(genericInstance, instance);
當然,您無法為Func提供實例參數,因此我不知道如何參數化Lambda生成。 有任何想法嗎?
我想你會使用Expression.Lambda
,它將委托類型作為一個類型而不是泛型,並像使用Gen<>
一樣動態創建你的Func:
MethodInfo mi = genericType.GetMethod("DoSomething",
BindingFlags.Instance | BindingFlags.Public);
var p1 = Expression.Parameter(genericType, "generic");
var p2 = Expression.Parameter(fieldType, "instance");
var func = typeof (Func<,,>);
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int));
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2),
new[] { p1, p2 }).Compile();
這將返回一個Delegate而不是一個強類型的Func
,但你當然可以在需要的時候拋出它(如果你不知道你要投射到什么,看起來很難),或者使用DynamicInvoke
動態調用它。
int answer = (int) x.DynamicInvoke(genericInstance, instance);
編輯 :
確實有效的好主意。 不幸的是,我想使用強類型編譯的Lambda的原因是性能。 與類型化的Lambda相比,使用DynamicInvoke非常慢。
這似乎無需動態調用即可工作。
var p1 = Expression.Parameter(genericType, "generic");
var p2 = Expression.Parameter(fieldType, "instance");
var func = typeof(Func<,,>);
var genericFunc = func.MakeGenericType(genericType, fieldType, typeof(int));
var x = Expression.Lambda(genericFunc, Expression.Call(p1, mi, p2), new[] { p1, p2 });
var invoke = Expression.Invoke(x, Expression.Constant(genericInstance), Expression.Constant(instance));
var answer = Expression.Lambda<Func<int>>(invoke).Compile()();
編輯2 :
大大簡化的版本:
Type fieldType = ;// This is the type I have discovered
Type genericType = typeof(Gen<>).MakeGenericType(fieldType);
object genericInstance = Activator.CreateInstance(genericType);
MethodInfo mi = genericType.GetMethod("DoSomething",
BindingFlags.Instance | BindingFlags.Public);
var value = Expression.Constant(instance, fieldType);
var lambda = Expression.Lambda<Func<int>>(Expression.Call(Expression.Constant(genericInstance), mi, value));
var answer = lambda.Compile()();
此答案僅適用於使用.NET 4.0的情況。
如果你使genericInstance
dynamic
而不是object
,你可以直接調用它上面的DoSomething
方法,動態語言運行庫將為你處理一切。
class Type1 {
public int DoSomething() { return 1; }
}
class Type2 {
public int DoSomething() { return 2; }
}
static void TestDynamic() {
dynamic t1 = Activator.CreateInstance(typeof(Type1));
int answer1 = t1.DoSomething(); // returns 1
dynamic t2 = Activator.CreateInstance(typeof(Type2));
int answer2 = t2.DoSomething(); // returns 2
}
如果你需要保留這個類結構( Gen<T>
),那么我沒有看到一個簡單的方法,即在編譯時你不知道類型T
如果要調用委托,則必須在編譯時知道其完整類型,或者需要將參數作為對象傳遞。
使用dynamic
可以隱藏獲取MethodInfo
等的復雜性,並為您提供出色的性能。 我看到的與DynamicInvoke
相比的一個缺點是,我相信你會得到為每個呼叫站點解析一次動態調用的初始開銷。 如果在具有相同類型的對象上調用綁定,則會緩存綁定,以便它們從第二次開始運行得非常快。
最好接受一個object
並使用convert
為已知類型。
下面是一個示例,如何在未知深度上按名稱構建對屬性的訪問:
var model = new { A = new { B = 10L } };
string prop = "A.B";
var parameter = Expression.Parameter(typeof(object));
Func<object, long> expr = (Func<object, long>) Expression.Lambda(prop.Split('.').Aggregate<string, Expression>(Expression.Convert(parameter, model.GetType()), Expression.Property), parameter).Compile();
expr(model).Dump();
當編譯時未知委托類型時,它可以避免額外的DynamicInvoke成本。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.