簡體   English   中英

如果直到運行時才知道類型,如何創建Expression.Lambda?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM