2011-10-18 22 views
12

Điều này được giải thích tốt nhất khi sử dụng mã. Tôi có một lớp chung chung có một phương thức trả về một số nguyên. Dưới đây là một phiên bản đơn giản nhằm mục đích giải thích ...Làm thế nào để tạo một Expression.Lambda khi một loại không được biết đến cho đến khi chạy?

public class Gen<T> 
{ 
    public int DoSomething(T instance) 
    { 
     // Real code does something more interesting! 
     return 1; 
    } 
} 

Khi chạy tôi sử dụng phản ánh để khám phá các loại một cái gì đó và sau đó muốn tạo một thể hiện của lớp Gen tôi cho rằng loại hình cụ thể. Điều đó thật dễ dàng và được thực hiện như thế này ...

Type fieldType = // This is the type I have discovered 
Type genericType = typeof(Gen<>).MakeGenericType(fieldType); 
object genericInstance = Activator.CreateInstance(genericType); 

Bây giờ tôi muốn tạo một Biểu thức sẽ lấy tham số là một thể hiện của loại chung và sau đó gọi phương thức DoSomething thuộc loại đó. Vì vậy, tôi muốn Expression để thực hiện có hiệu quả này ...

int answer = genericInstance.DoSomething(instance); 

... ngoại trừ tôi không có 'dụ' cho đến khi một số điểm sau khi chạy và genericInstance là loại được tạo ra như có thể thấy ở trên. nỗ lực của tôi lúc tạo Lambda của việc này là như sau ...

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(); 

... để sau này tôi có thể gọi nó với một cái gì đó như thế này ...

int answer = x(genericInstance, instance); 

Tất nhiên, bạn không thể cung cấp Func với các tham số cá thể và vì vậy tôi không có ý tưởng làm thế nào để tham số hóa thế hệ Lambda. Bất kỳ ý tưởng?

Trả lời

18

Tôi nghĩ rằng bạn sẽ chỉ cần sử dụng Expression.Lambda mà có kiểu delegate như một kiểu thay vì sau đó là một generic, và tạo Func của bạn một cách nhanh chóng như bạn có Gen<>:

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(); 

này sẽ trả về một Ủy quyền thay vì gõ mạnh Func, nhưng bạn có thể bỏ nó nếu cần thiết (và có vẻ khó khăn nếu bạn không biết bạn đang đúc gì), hoặc tự động gọi nó bằng cách sử dụng DynamicInvoke trên đó.

int answer = (int) x.DynamicInvoke(genericInstance, instance); 

EDIT:

Một ý tưởng tốt mà không thực sự làm việc. Thật không may là lý do tôi muốn sử dụng một lambda biên soạn mạnh mẽ là hiệu suất. Sử dụng DynamicInvoke là khá chậm so với Lambda đã nhập.

Điều này dường như hoạt động mà không cần có lời gọi động.

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()(); 

EDIT 2:

Một phiên bản đơn giản hóa đáng kể:

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()(); 
+0

Một ý tưởng hay thực sự hiệu quả. Thật không may là lý do tôi muốn sử dụng một lambda biên soạn mạnh mẽ là hiệu suất. Sử dụng DynamicInvoke là khá chậm so với Lambda đã nhập. –

+0

Có thể chụp các biến trong cây Biểu thức không? Tôi sẽ giúp nắm bắt genericInstance vì điều đó sẽ không bao giờ thay đổi. –

+0

@PhilWright Hm, tôi hiểu rồi. Hãy để tôi xem những gì khác tôi có thể đến với. – vcsjones

1

Câu trả lời này chỉ áp dụng nếu bạn đang sử dụng .NET 4.0.

Nếu bạn thực hiện genericInstancedynamic thay vì object, sau đó bạn có thể gọi phương thức DoSomething vào nó trực tiếp, và thời gian chạy ngôn ngữ năng động sẽ chăm sóc tất cả mọi thứ cho bạn.

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 
} 

Nếu bạn cần để giữ cấu trúc lớp này (Gen<T>), sau đó tôi không thấy một cách dễ dàng trên thực tế là bạn không biết loại T tại thời gian biên dịch. Nếu bạn muốn gọi đại biểu, bạn phải biết kiểu đầy đủ của nó tại thời gian biên dịch, hoặc bạn cần truyền vào các tham số như các đối tượng.

Sử dụng dynamic giúp bạn ẩn sự phức tạp khi nhận được MethodInfo, v.v., và mang đến cho bạn hiệu suất tuyệt vời. Một nhược điểm so với DynamicInvoke mà tôi thấy là tôi tin rằng bạn nhận được chi phí ban đầu để giải quyết cuộc gọi năng động một lần cho mọi trang web cuộc gọi. Các ràng buộc được lưu trữ để chúng chạy rất nhanh từ lần thứ hai trở đi nếu bạn gọi nó trên các đối tượng có cùng kiểu.

0

Tốt hơn là chấp nhận số object và sử dụng convert cho loại đã biết.

Dưới đây là một ví dụ, làm thế nào để xây dựng quyền truy cập vào thuộc tính có tên trên chưa biết sâu:

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(); 

Nó tránh được chi phí phụ trội của DynamicInvoke khi loại đại biểu là chưa có thời gian biên dịch.

Các vấn đề liên quan