2010-09-18 5 views
21

Trong C# 3.0, bạn có thể sử dụng biểu thức để tạo ra một lớp học với cú pháp sau:Làm thế nào để sử dụng Biểu thức để xây dựng một Kiểu ẩn danh?

var exp = Expression.New(typeof(MyClass)); 
var lambda = LambdaExpression.Lambda(exp); 
object myObj = lambda.Compile().DynamicInvoke(); 

Nhưng làm thế nào để bạn sử dụng biểu thức để tạo ra một lớp Anonymous?

//anonymousType = typeof(new{ Name="abc", Num=123}); 
Type anonymousType = Expression.NewAnonymousType??? <--How to do ? 
var exp = Expression.New(anonymousType); 
var lambda = LambdaExpression.Lambda(exp); 
object myObj = lambda.Compile().DynamicInvoke(); 
+0

Có thể ghép đôi: http://stackoverflow.com/questions/606104/linq-expression-tree-question –

+1

@Flash, điều này là không thể, ít nhất là không trực tiếp. Trình biên dịch thực hiện rất nhiều "phép thuật" cho bạn khi bạn tạo các kiểu vô danh - đó là cú pháp cú pháp để thực sự khai báo một lớp C# chính hãng với một loạt các thuộc tính. Trình biên dịch chỉ làm tất cả điều này cho bạn. Không có loại cây biểu thức nào thực sự thực hiện tất cả điều này cho bạn một cách tự động. Nếu bạn nhìn vào liên kết tôi đã tham chiếu, nó cung cấp một giải pháp thay thế. Tuy nhiên, nó sử dụng Reflection.Emit, mà không phải là cho feint của trái tim. –

+1

Kirk: OP muốn * xây dựng * một lớp ẩn danh, không * tạo * một từ đầu. Miễn là anh ta biết thời gian biên dịch tên và kiểu của thuộc tính là gì, anh ta có thể lấy trình biên dịch để tạo kiểu cho anh ta và tất cả những gì anh ta phải làm là tìm ra cách để thể hiện nó. – Gabe

Trả lời

17

Bạn đang gần, nhưng bạn phải lưu ý rằng kiểu nặc danh không có nhà thầu mặc định. Các bản in mã sau { Name = def, Num = 456 }:

Type anonType = new { Name = "abc", Num = 123 }.GetType(); 
var exp = Expression.New(
      anonType.GetConstructor(new[] { typeof(string), typeof(int) }), 
      Expression.Constant("def"), 
      Expression.Constant(456)); 
var lambda = LambdaExpression.Lambda(exp); 
object myObj = lambda.Compile().DynamicInvoke(); 
Console.WriteLine(myObj); 

Nếu bạn không cần phải tạo ra nhiều trường hợp thuộc loại này, Activator.CreateInstance sẽ làm chỉ là tốt (đó là nhanh hơn trong một vài trường hợp, nhưng chậm hơn nhiều). Mã này in { Name = ghi, Num = 789 }:

Type anonType = new { Name = "abc", Num = 123 }.GetType(); 
object myObj = Activator.CreateInstance(anonType, "ghi", 789); 
Console.WriteLine(myObj); 
+2

Nhưng, gõ anonType = new {Name = "abc", Num = 123} .GetType() ; <- Đó là mã tĩnh, không phải là mã động. – Flash

+2

@Flash: Nếu bạn có ấn tượng rằng mã C# 'mới {Name =" abc ", Num = 123}', khi được sử dụng trong biểu thức LINQ, tạo kiểu mới lúc chạy, sau đó bạn bị nhầm lẫn. Trình biên dịch tạo kiểu tại thời gian biên dịch, và cây biểu hiện được tạo ra không thể phân biệt được với một loại sử dụng một kiểu không ẩn danh. – Timwi

+0

Flash: Bạn muốn * các loại * ẩn danh năng động? Bạn định làm gì với họ? – Gabe

6

Kể từ khi một loại vô danh không có một constructor rỗng mặc định, bạn không thể sử dụng quá tải Expression.New(Type) ... bạn phải cung cấp các thông số ConstructorInfo và phương pháp Expression.New. Để thực hiện điều đó, bạn phải có thể lấy Type ..., do đó bạn cần tạo một thể hiện "stub" của kiểu ẩn danh và sử dụng nó để lấy TypeConstructorInfo, sau đó chuyển các tham số tới Expression.New.

Như thế này:

var exp = Expression.New(new { Name = "", Num = 0 }.GetType().GetConstructors()[0], 
         Expression.Constant("abc", typeof(string)), 
         Expression.Constant(123, typeof(int))); 
var lambda = LambdaExpression.Lambda(exp); 
object myObj = lambda.Compile().DynamicInvoke(); 
+1

Đây là một giải pháp thông minh. Nhưng thường là lý do người ta cần phải viết một cái gì đó bằng cách sử dụng cây biểu hiện (API) là chính xác vì một * không * có thông tin này tại thời gian biên dịch. Nếu có, họ sẽ sử dụng các biểu thức C# bình thường ngay từ đầu. –

+0

@Kirk Mã OP cầu xin khác nhau. Có rất nhiều tình huống mà bạn biết loại nhưng vẫn phải xây dựng ExpressionTree. DynamicLinq-2-Sql cho một –

+0

Chỉ cần nitpicking, các kiểu vô danh có các hàm tạo rỗng nếu kiểu ẩn danh là 'new {}' :) – nawfal

3

Bạn có thể tránh sử dụng DynamicInvoke đó là đau đớn chậm. Bạn có thể sử dụng suy luận kiểu trong C# để có được kiểu ẩn danh của bạn được khởi tạo một cách tổng quát. Một cái gì đó như:

public static Func<object[], T> AnonymousInstantiator<T>(T example) 
{ 
    var ctor = typeof(T).GetConstructors().First(); 
    var paramExpr = Expression.Parameter(typeof(object[])); 
    return Expression.Lambda<Func<object[], T>> 
    (
     Expression.New 
     (
      ctor, 
      ctor.GetParameters().Select 
      (
       (x, i) => Expression.Convert 
       (
        Expression.ArrayIndex(paramExpr, Expression.Constant(i)), 
        x.ParameterType 
       ) 
      ) 
     ), paramExpr).Compile(); 
} 

Bây giờ bạn có thể gọi điện,

var instantiator = AnonymousInstantiator(new { Name = default(string), Num = default(int) }); 

var a1 = instantiator(new object[] { "abc", 123 }); // strongly typed 
var a2 = instantiator(new object[] { "xyz", 789 }); // strongly typed 
// etc. 

Bạn có thể sử dụng phương pháp AnonymousInstantiator để tạo ra các chức năng để nhanh chóng bất kỳ loại vô danh với bất kỳ số lượng tài sản, chỉ mà bạn phải vượt qua một thích hợp ví dụ trước. Các tham số đầu vào phải được chuyển thành một mảng đối tượng. Nếu bạn lo lắng hiệu suất boxing ở đó thì bạn phải viết một instantiator tùy chỉnh chỉ chấp nhận các thông số đầu vào là stringint, nhưng việc sử dụng một instantiator như vậy sẽ bị giới hạn hơn một chút.