2012-06-23 25 views
8

Tôi có một phương pháp mà tôi muốn chuyển đổi sang phương thức mở rộngThông báo lỗi "Nhà điều hành '.' không thể được áp dụng cho toán hạng kiểu 'lambda expression' "khi chuyển đổi phương thức sang Phương thức mở rộng?

public static string GetMemberName<T>(Expression<Func<T>> item) 
{ 
    return ((MemberExpression)item.Body).Member.Name; 
} 

và gọi nó như

string str = myclass.GetMemberName(() => new Foo().Bar); 

nên đánh giá để str = "Bar"; // It gives the Member name and not its value

Bây giờ khi tôi cố gắng để chuyển đổi này để mở rộng theo phương thức này

public static string GetMemberName<T>(this Expression<Func<T>> item) 
{ 
    return ((MemberExpression)item.Body).Member.Name; 
} 

và gọi nó là như

string str = (() => new Foo().Bar).GetMemberName(); 

Lỗi nói Operator '.' cannot be applied to operand of type 'lambda expression'

Tôi đang ở đâu vậy?

Trả lời

7

Có thực sự hai điều ở đây, lần đầu tiên, đi qua () => new Foo().Bar vào phương pháp mà chấp nhận Expression<Func<T>> xử lý cây biểu thức quy định như một Expression<Func<T>>, nhưng () => new Foo().Bar không phải là một Expression<Func<T>> ngày của riêng mình .

Thứ hai, để phương thức tiện ích của bạn chấp nhận bất kỳ lambda nào (chẳng hạn như bạn đang cung cấp), bạn phải sử dụng loại tương ứng với bất kỳ cây biểu thức nào. Tuy nhiên, như bạn có thể đã đoán dựa trên thông báo ... to operand of type 'lambda expression' nơi bạn thường thấy tên của loại bên trong dấu ngoặc kép, biểu thức lambda được xử lý đặc biệt theo ngôn ngữ, làm cho những gì bạn đang cố gắng thực hiện mà không cần truyền trước , Không thể nào.

Cách để gọi phương pháp mở rộng của bạn ở dạng phương pháp khuyến nông sẽ được (trong trường hợp đó Bar là loại string)

((Expression<Func<string>>)(() => new Foo().Bar)).GetMemberName()` 

mà không có vẻ như nó sẽ là tất cả những gì mong muốn.

+0

Làm việc cho tôi .. –

+0

Tôi không chắc chắn tôi làm theo bình luận của bạn, nó làm việc cho bạn mà không cần đúc đầu tiên để cụ thể 'Expression >' hoặc kiểu gọi này là thỏa đáng? – mlorbetske

+0

Câu trả lời của bạn làm việc cho tôi '((Biểu hiện >) (() => new Foo(). Bar)) GetMemberName()' –

6

Tôi đang ở đâu sai?

Trình biên dịch cho bạn biết chính xác điều gì không đúng - bạn không thể sử dụng . trên biểu thức lambda.

Biểu thức lambda không có bất kỳ loại cụ thể nào - chỉ cần chuyển đổi thành cây biểu thức.

Một thành viên truy cập biểu thức (đó là những gì bạn đang cố gắng để làm) chỉ có sẵn trong các hình thức

primary-expression . identifier type-argument-list(opt) 
predefined-type . identifier type-argument-list(opt) 
qualified-alias-member . identifier type-argument-list(opt) 

... và một biểu thức lambda không phải là một biểu thức chính.

Thú vị, đối số này không giữ cho biểu thức ẩn danh, nhưng đối với bạn vẫn không thể sử dụng biểu thức truy cập thành viên trên đó. Mục 7.6.4 của thông số C# liệt kê cách biểu thức truy cập thành viên bị ràng buộc và phần lớn tùy chọn nằm trong "Nếu E là được xác định trước loại hoặc biểu thức chính được phân loại là loại" (không áp dụng cho các phương thức nặc danh) hoặc "Nếu E là một thuộc tính truy cập, biến hoặc giá trị, kiểu đó là T" - nhưng một phương thức nặc danh là một hàm ẩn danh và theo phần 7,15: "Một hàm ẩn danh không không có giá trị hoặc loại của chính nó ".

EDIT: Bạn có thể vẫn sử dụng các phương pháp mở rộng trên cây biểu thức, bạn không thể sử dụng chúng trực tiếp trên biểu thức lambda. Vì vậy, điều này sẽ hoạt động:

Expression<Func<int>> expr =() => new Foo().Bar; 
string name = expr.GetMemberName(); 

... nhưng rõ ràng là không hữu ích. (Ditto với một dàn diễn viên như mỗi câu trả lời mlorbetske của.)

+0

Cảm ơn, nhưng điều này không có nghĩa là phương pháp này không thể được chuyển đổi sang EM? –

+0

@NikhilAgrawal: Vâng, bạn có thể làm điều đó - nhưng không phải trong một bước. –

+0

hoặc như @mlorbetske trả lời '((Biểu hiện >) (() => mới Foo(). Bar)). GetMemberName(); ' –

2

Để nhận được biểu thức đã nhập, bạn sẽ phải viết nó ra. Như những người khác đã nói, không có cách nào trình biên dịch sẽ tự động suy ra nó từ một biểu thức lambda kể từ khi một biểu thức lambda có thể có nghĩa là hai điều - hoặc là một đại biểu hoặc một cây biểu thức.

Bạn có thể nhận được các biểu hiện tương đối đơn giản, bằng cách cho phép trình biên dịch suy ra kiểu cho bạn một phần, giống như (from this answer):

public sealed class Lambda 
{ 
    public static Func<T> Func<T>(Func<T> func) 
    { 
     return func; 
    } 

    public static Expression<Func<T>> Expression<T>(Expression<Func<T>> expression) 
    { 
     return expression; 
    } 
} 

public sealed class Lambda<S> 
{ 
    public static Func<S, T> Func<T>(Func<S, T> func) 
    { 
     return func; 
    } 

    public static Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression) 
    { 
     return expression; 
    } 
} 

//etc, to cover more cases 

Gọi nó thích:

var expr1 = Lambda.Expression(() => new Foo().Bar); 
var expr2 = Lambda<string>.Expression(x => x.Length); //etc 

của bạn tùy chọn là:

  1. Truyền ngược, đến loại biểu thức chính xác và sau đó gọi phương thức mở rộng trên đó

    var name = ((Expression<Func<BarType>>)(() => new Foo().Bar)).GetMemberName(); 
    

    trông xấu xí - chữa bệnh tệ hơn nguyên nhân.

  2. Lấy biểu hiện đầu tiên vào một biến

    Expression<Func<BarType>> expr =() => new Foo().Bar; 
    var name = expr.GetMemberName(); 
    

    Hơi tốt hơn, nhưng vẫn có vẻ ít off cho một điều tầm thường.

  3. Sử dụng Lambda lớp viết ở trên

    var name = Lambda.Expression(() => new Foo().Bar).GetMemberName(); 
    

    Thậm chí tốt hơn. Nó chỉ là một chút ít gõ.

  4. mẫu đầu tiên của bạn, mà tôi nghĩ là tốt nhất mà bạn có thể nhận được như xa như khả năng đọc đi

    Tôi không nghĩ rằng bạn có thể cải thiện khi mà quy tắc xem xét C# liên quan đến biểu thức lambda. Điều đó nói rằng tôi nghĩ rằng vài cải tiến có thể được thực hiện.

    Trước tiên, hãy mở rộng chức năng cho các loại biểu thức lambda khác. Bạn sẽ cần nhiều hơn Func<T> loại để xử lý tất cả các trường hợp. Quyết định các loại biểu thức bạn phải xử lý là gì. Ví dụ: nếu bạn có loại Func<S, T> (như trong câu hỏi của bạn - Bar trên Foo), có vẻ tốt hơn.So sánh này

    myclass.GetMemberName(() => new Foo().Bar); 
    

    với

    myclass.GetMemberName<Foo>(x => x.Bar); 
    

    tôi sẽ nói những quá tải sẽ làm gì:

    //for static methods which return void 
    public static string GetMemberName(Expression<Action> expr); 
    
    //for static methods which return non-void and properties and fields 
    public static string GetMemberName<T>(Expression<Func<T>> expr); 
    
    //for instance methods which return void 
    public static string GetMemberName<T>(Expression<Action<T>> expr); 
    
    //for instance methods which return non-void and properties and fields 
    public static string GetMemberName<S, T>(Expression<Func<S, T>> expr); 
    

    Bây giờ chúng có thể được sử dụng không chỉ trong các trường hợp nêu trong ý kiến, chắc chắn có những chồng chéo kịch bản. Ví dụ: nếu bạn đã có phiên bản Foo, thì sẽ dễ dàng hơn khi gọi quá tải thứ hai (Func<T>) quá tải cho tên thuộc tính Bar, như myclass.GetMemberName(() => foo.Bar).

    Thứ hai, triển khai chức năng GetMemberName phổ biến cho tất cả các tình trạng quá tải này. Phương thức tiện ích mở rộng trên Expression<T> hoặc LambdaExpression sẽ thực hiện. Tôi thích thứ hai để bạn có thể gọi nó ngay cả trong các kịch bản không được đánh máy mạnh. Tôi sẽ viết nó như thế này, from this answer:

    public static string GetMemberName(this LambdaExpression memberSelector) 
    { 
        Func<Expression, string> nameSelector = null; 
        nameSelector = e => //or move the entire thing to a separate recursive method 
        { 
         switch (e.NodeType) 
         { 
          case ExpressionType.Parameter: 
           return ((ParameterExpression)e).Name; 
          case ExpressionType.MemberAccess: 
           return ((MemberExpression)e).Member.Name; 
          case ExpressionType.Call: 
           return ((MethodCallExpression)e).Method.Name; 
          case ExpressionType.Convert: 
          case ExpressionType.ConvertChecked: 
           return nameSelector(((UnaryExpression)e).Operand); 
          case ExpressionType.Invoke: 
           return nameSelector(((InvocationExpression)e).Expression); 
          case ExpressionType.ArrayLength: 
           return "Length"; 
          default: 
           throw new Exception("not a proper member selector"); 
         } 
        }; 
    
        return nameSelector(memberSelector.Body); 
    } 
    

    Cuối cùng, GetMemberName không phải là một tên tốt nếu bạn gọi nó là không mở rộng đường. expression.GetMemberName() âm thanh hợp lý hơn. Member.NameFrom<int>(x => x.ToString()) hoặc MemberName.From<string>(x => x.Length) vv là các tên mô tả hơn cho các cuộc gọi tĩnh.

    Vì vậy, tổng thể lớp có thể trông giống như:

    public static class Member 
    { 
        public static string NameFrom(Expression<Action> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Func<T>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Action<T>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Func<T, object>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    } 
    

    Và sử dụng:

    var name1 = Member.NameFrom(() => Console.WriteLine()); 
    var name2 = Member.NameFrom(() => Environment.ExitCode); 
    var name3 = Member.NameFrom<Control>(x => x.Invoke(null)); 
    var name4 = Member.NameFrom<string>(x => x.Length); 
    

    ngắn gọn nhất và sạch sẽ.

  5. Đối với tài sản và các lĩnh vực, nó có thể được chuyển thành một lớp vô danh và sau đó sử dụng phản ánh tên thành viên có thể được đọc, as shown here.

    public static string GetMemberName<T>(T item) where T : class 
    { 
        if (item == null) 
         return null; 
    
        return typeof(T).GetProperties()[0].Name; 
    } 
    

    Gọi nó như

    var name = GetMemberName(new { new Foo().Bar }); 
    

    Nó nhanh hơn, nhưng có những quirks nhất định, giống như không phải là rất thân thiện với refactor, và không giúp đỡ trong trường hợp các phương pháp như các thành viên. Xem các chủ đề ..

Trong tất cả Tôi thích 4.

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