chuyển đổi .net Func <T> thành .net Expression <Func <T>>


118

Chuyển từ lambda sang Biểu thức thật dễ dàng bằng cách sử dụng một cuộc gọi phương thức ...

public void GimmeExpression(Expression<Func<T>> expression)
{
    ((MemberExpression)expression.Body).Member.Name; // "DoStuff"
}

public void SomewhereElse()
{
    GimmeExpression(() => thing.DoStuff());
}

Nhưng tôi muốn chuyển Hàm thành một biểu thức, chỉ trong một số trường hợp hiếm hoi ...

public void ContainTheDanger(Func<T> dangerousCall)
{
    try 
    {
        dangerousCall();
    }
    catch (Exception e)
    {
        // This next line does not work...
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

Dòng không hoạt động mang lại cho tôi lỗi thời gian biên dịch Cannot implicitly convert type 'System.Func<T>' to 'System.Linq.Expressions.Expression<System.Func<T>>'. Diễn viên rõ ràng không giải quyết được tình huống. Có cơ sở nào để làm việc này mà tôi đang bỏ qua không?


Tôi thực sự không thấy sử dụng nhiều cho ví dụ 'trường hợp hiếm hoi'. Người gọi đang chuyển trong Hàm <T>. Không cần phải lặp lại với người gọi Func <T> đó là gì (thông qua ngoại lệ).
Adam Ralph

2
Ngoại lệ không được xử lý trong trình gọi. Và, vì có nhiều trang web cuộc gọi đi qua các Hàm <T> khác nhau, việc bắt ngoại lệ trong trình gọi sẽ tạo ra sự trùng lặp.
Dave Cameron

1
Dấu vết ngăn xếp ngoại lệ được thiết kế để hiển thị thông tin này. Nếu ngoại lệ được ném trong lời gọi của Func <T>, điều này sẽ hiển thị trong dấu vết ngăn xếp. Ngẫu nhiên, nếu bạn chọn đi theo cách khác, tức là chấp nhận một biểu thức và biên dịch nó để gọi, bạn sẽ mất điều này vì dấu vết ngăn xếp sẽ hiển thị một cái gì đó giống như at lambda_method(Closure )lệnh gọi của đại biểu đã biên dịch.
Adam Ralph

Tôi đoán bạn nên nhìn vào các câu trả lời trong [link] này [1] [1]: stackoverflow.com/questions/9377635/create-expression-from-func/...
Ibrahim Kais Ibrahim

Câu trả lời:


104

Ồ, không dễ chút nào. Func<T>đại diện cho một delegatebiểu thức chung chứ không phải một biểu thức. Nếu có bất kỳ cách nào bạn có thể làm như vậy (do tối ưu hóa và những thứ khác được thực hiện bởi trình biên dịch, một số dữ liệu có thể bị loại bỏ, vì vậy có thể không thể lấy lại biểu thức ban đầu), nó sẽ được tháo rời IL ngay lập tức và suy ra biểu thức (không có nghĩa là dễ dàng). Xử lý các biểu thức lambda dưới dạng data ( Expression<Func<T>>) là một phép thuật được thực hiện bởi trình biên dịch (về cơ bản trình biên dịch xây dựng một cây biểu thức trong mã thay vì biên dịch nó thành IL).

Sự thật liên quan

Đây là lý do tại sao các ngôn ngữ đẩy lambdas đến cùng cực (như Lisp) thường dễ thực hiện hơn với vai trò thông dịch viên . Trong các ngôn ngữ đó, mã và dữ liệu về cơ bản là giống nhau (ngay cả khi đang chạy ), nhưng chip của chúng ta không thể hiểu dạng mã đó, vì vậy chúng ta phải mô phỏng một máy như vậy bằng cách xây dựng một trình thông dịch trên đầu nó để hiểu nó ( lựa chọn được thực hiện bởi Lisp như ngôn ngữ) hoặc hy sinh sức mạnh (mã sẽ không còn chính xác bằng dữ liệu) ở một mức độ nào đó (lựa chọn được thực hiện bởi C #). Trong C #, trình biên dịch tạo ảo giác coi mã là dữ liệu bằng cách cho phép lambdas được hiểu là ( Func<T>) và dữ liệu ( Expression<Func<T>>) tại thời điểm biên dịch .


3
Lisp không cần phải thông dịch, nó có thể dễ dàng được biên dịch. Macro sẽ phải được mở rộng tại thời điểm biên dịch, và nếu bạn muốn hỗ trợ, evalbạn sẽ cần phải khởi động trình biên dịch, nhưng ngoài ra, không có vấn đề gì khi làm điều đó.
cấu hình

2
"Biểu thức <Func <T>> DangerousExpression = () => riskCall ();" No không hê dê dang?
mheyman

10
@mheyman Điều đó sẽ tạo mới Expressionvề hành động trình bao bọc của bạn, nhưng nó sẽ không có thông tin cây biểu thức về nội bộ của dangerousCallđại biểu.
Nenad

34
    private static Expression<Func<T, bool>> FuncToExpression<T>(Func<T, bool> f)  
    {  
        return x => f(x);  
    } 

1
Tôi muốn duyệt qua cây cú pháp của biểu thức được trả về. Cách tiếp cận này có cho phép tôi làm điều đó không?
Dave Cameron

6
@DaveCameron - Không. Xem các câu trả lời ở trên - câu trả lời đã được biên dịch Funcsẽ bị ẩn trong Biểu thức mới. Điều này chỉ đơn giản là thêm một lớp dữ liệu qua mã; bạn có thể lướt qua một lớp chỉ để tìm thông số của mình fmà không cần thêm chi tiết, vì vậy bạn đang ở ngay nơi bắt đầu.
Jonno

21

Điều bạn có thể nên làm, là xoay chuyển phương pháp. Lấy một Biểu thức>, biên dịch và chạy. Nếu nó không thành công, bạn đã có Biểu thức để xem xét.

public void ContainTheDanger(Expression<Func<T>> dangerousCall)
{
    try 
    {
        dangerousCall().Compile().Invoke();;
    }
    catch (Exception e)
    {
        // This next line does not work...
        var nameOfDanger = 
            ((MemberExpression)dangerousCall.Body).Member.Name;
        throw new DangerContainer(
            "Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    ContainTheDanger(() => thing.CrossTheStreams());
}

Rõ ràng là bạn cần phải xem xét các tác động hiệu suất của việc này và xác định xem đó có phải là điều bạn thực sự cần làm hay không.


7

Tuy nhiên, bạn có thể đi theo cách khác thông qua phương thức .Compile () - không chắc liệu điều này có hữu ích cho bạn hay không:

public void ContainTheDanger<T>(Expression<Func<T>> dangerousCall)
{
    try
    {
        var expr = dangerousCall.Compile();
        expr.Invoke();
    }
    catch (Exception e)
    {
        Expression<Func<T>> DangerousExpression = dangerousCall;
        var nameOfDanger = ((MethodCallExpression)dangerousCall.Body).Method.Name;
        throw new DangerContainer("Danger manifested while " + nameOfDanger, e);
    }
}

public void SomewhereElse()
{
    var thing = new Thing();
    ContainTheDanger(() => thing.CrossTheStreams());
}

6

Nếu đôi khi bạn cần một biểu thức và đôi khi cần một đại biểu, bạn có 2 lựa chọn:

  • có các phương pháp khác nhau (1 cho mỗi phương pháp)
  • luôn chấp nhận Expression<...>phiên bản và chỉ .Compile().Invoke(...)nó nếu bạn muốn có người ủy quyền. Rõ ràng điều này có chi phí.

6

NJection.LambdaConverter là một thư viện chuyển đổi các đại biểu thành biểu thức

public class Program
{
    private static void Main(string[] args) {
       var lambda = Lambda.TransformMethodTo<Func<string, int>>()
                          .From(() => Parse)
                          .ToLambda();            
    }   

    public static int Parse(string value) {
       return int.Parse(value)
    } 
}

4
 Expression<Func<T>> ToExpression<T>(Func<T> call)
        {
            MethodCallExpression methodCall = call.Target == null
                ? Expression.Call(call.Method)
                : Expression.Call(Expression.Constant(call.Target), call.Method);

            return Expression.Lambda<Func<T>>(methodCall);
        }

Bạn có thể nói rõ hơn về phần "điều này sẽ không hoạt động"? Bạn đã thực sự thử biên dịch và thực thi nó chưa? Hoặc nó không hoạt động đặc biệt trong ứng dụng của bạn?
Dmitry Dzygin

1
FWIW, đây có thể không phải là vé chính, nhưng đó là thứ tôi cần. Đó là call.Targetphần đã giết chết tôi. Nó hoạt động trong nhiều năm, và sau đó đột nhiên ngừng hoạt động và bắt đầu phàn nàn về một blah blah tĩnh / không tĩnh. Dù sao cũng cảm ơn!
Eli Gassert


-1

Thay đổi

// This next line does not work...
Expression<Func<T>> DangerousExpression = dangerousCall;

Đến

// This next line works!
Expression<Func<T>> DangerousExpression = () => dangerousCall();

Servy, đó là cách hoàn toàn hợp pháp để có được một biểu hiện. đường cú pháp để xây dựng nó thông qua biểu thức .lambda và biểu thức.call. Bạn nghĩ tại sao nó sẽ bị lỗi trong thời gian chạy?
Roman Pokrovskij
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.