Tại sao một số biểu thức lambda trong C # lại biên dịch thành phương thức tĩnh?


122

Như bạn có thể thấy trong đoạn mã bên dưới, tôi đã khai báo một Action<>đối tượng là một biến.

Ai đó vui lòng cho tôi biết tại sao đại biểu phương thức hành động này hoạt động giống như một phương thức tĩnh?

Tại sao nó trả về truetrong đoạn mã sau?

Mã:

public static void Main(string[] args)
{
    Action<string> actionMethod = s => { Console.WriteLine("My Name is " + s); };

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

Đầu ra:

ví dụ đầu ra của mẫu

Câu trả lời:


153

Điều này rất có thể là do không có đóng cửa, ví dụ:

int age = 25;
Action<string> withClosure = s => Console.WriteLine("My name is {0} and I am {1} years old", s, age);
Action<string> withoutClosure = s => Console.WriteLine("My name is {0}", s);
Console.WriteLine(withClosure.Method.IsStatic);
Console.WriteLine(withoutClosure.Method.IsStatic);

Điều này sẽ xuất ra falsecho withClosuretruecho withoutClosure.

Khi bạn sử dụng biểu thức lambda, trình biên dịch sẽ tạo ra một lớp nhỏ để chứa phương thức của bạn, điều này sẽ biên dịch thành một thứ gì đó giống như sau (cách triển khai thực tế có thể sẽ thay đổi một chút):

private class <Main>b__0
{
    public int age;
    public void withClosure(string s)
    {
        Console.WriteLine("My name is {0} and I am {1} years old", s, age)
    }
}

private static class <Main>b__1
{
    public static void withoutClosure(string s)
    {
        Console.WriteLine("My name is {0}", s)
    }
}

public static void Main()
{
    var b__0 = new <Main>b__0();
    b__0.age = 25;
    Action<string> withClosure = b__0.withClosure;
    Action<string> withoutClosure = <Main>b__1.withoutClosure;
    Console.WriteLine(withClosure.Method.IsStatic);
    Console.WriteLine(withoutClosure.Method.IsStatic);
}

Bạn có thể thấy các Action<string>trường hợp kết quả thực sự trỏ đến các phương thức trên các lớp được tạo này.


4
+1. Có thể xác nhận - không cần đóng cửa họ là ứng cử viên hoàn hảo cho staticcác phương pháp.
Simon Whitehead

3
Tôi chỉ định gợi ý rằng câu hỏi này cần một chút mở rộng, tôi quay lại và nó ở đó. Rất nhiều thông tin - thật tuyệt khi xem trình biên dịch đang làm gì dưới các trang bìa.
Liath

4
@Liath Ildasmthực sự hữu ích để hiểu những gì đang thực sự diễn ra, tôi có xu hướng sử dụng ILtab LINQPadđể kiểm tra các mẫu nhỏ.
Lukazoid

@Lukazoid Bạn vui lòng cho chúng tôi biết làm cách nào bạn có được đầu ra trình biên dịch này? ILDASM sẽ không đưa ra kết quả như vậy .. Bằng bất kỳ công cụ HOẶC phần mềm nào?
nunu

8
@nunu Trong ví dụ này, tôi đã sử dụng ILtab LINQPadvà suy ra C #. Một số tùy chọn để nhận được tương đương C # thực tế của đầu ra đã biên dịch sẽ được sử dụng ILSpyhoặc Reflectortrên hợp ngữ đã biên dịch, rất có thể bạn sẽ cần phải tắt một số tùy chọn sẽ cố gắng hiển thị lambdas chứ không phải các lớp được tạo bởi trình biên dịch.
Lukazoid

20

"Phương thức hành động" chỉ tĩnh như một tác dụng phụ của việc triển khai. Đây là một trường hợp của một phương thức ẩn danh không có biến được bắt. Vì không có biến nào được nắm bắt, nên phương pháp không có yêu cầu bổ sung về thời gian tồn tại ngoài những yêu cầu đối với biến cục bộ nói chung. Nếu nó đã tham chiếu đến các biến cục bộ khác, thời gian tồn tại của nó kéo dài đến thời gian tồn tại của các biến khác đó (xem phần L.1.7, Các biến cục bộ và phần N.15.5.1, Các biến bên ngoài được chụp , trong đặc tả C # 5.0).

Lưu ý rằng đặc tả C # chỉ nói về các phương thức ẩn danh được chuyển đổi thành "cây biểu thức", không phải "các lớp ẩn danh". Mặc dù cây biểu thức có thể được biểu diễn dưới dạng các lớp C # bổ sung, ví dụ, trong trình biên dịch của Microsoft, việc triển khai này không bắt buộc (như được thừa nhận bởi giây M.5.3 trong đặc tả C # 5.0). Do đó, vẫn chưa xác định được liệu hàm ẩn danh có tĩnh hay không. Hơn nữa, phần K.6 bỏ ngỏ nhiều chi tiết về cây biểu hiện.


2
+1 hành vi này rất có thể không được dựa vào, vì những lý do đã nêu; nó là rất nhiều chi tiết thực hiện.
Lukazoid

18

Hành vi lưu trong bộ nhớ đệm của đại biểu đã được thay đổi trong Roslyn. Trước đây, như đã nêu, bất kỳ biểu thức lambda nào không nắm bắt các biến đều được biên dịch thành một staticphương thức tại trang web cuộc gọi. Roslyn đã thay đổi hành vi này. Bây giờ, bất kỳ lambda nào, có nắm bắt các biến hay không, đều được chuyển đổi thành một lớp hiển thị:

Đưa ra ví dụ này:

public class C
{
    public void M()
    {
        var x = 5;
        Action<int> action = y => Console.WriteLine(y);
    }
}

Đầu ra trình biên dịch gốc:

public class C
{
    [CompilerGenerated]
    private static Action<int> CS$<>9__CachedAnonymousMethodDelegate1;
    public void M()
    {
        if (C.CS$<>9__CachedAnonymousMethodDelegate1 == null)
        {
            C.CS$<>9__CachedAnonymousMethodDelegate1 = new Action<int>(C.<M>b__0);
        }
        Action<int> arg_1D_0 = C.CS$<>9__CachedAnonymousMethodDelegate1;
    }
    [CompilerGenerated]
    private static void <M>b__0(int y)
    {
        Console.WriteLine(y);
    }
}

Roslyn:

public class C
{
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0
    {
        public static readonly C.<>c__DisplayClass0 CS$<>9__inst;
        public static Action<int> CS$<>9__CachedAnonymousMethodDelegate2;
        static <>c__DisplayClass0()
        {
            // Note: this type is marked as 'beforefieldinit'.
            C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0();
        }
        internal void <M>b__1(int y)
        {
            Console.WriteLine(y);
        }
    }
    public void M()
    {
        Action<int> arg_22_0;
        if (arg_22_0 = C.
                       <>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null)
        {
            C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 =
          new Action<int>(C.<>c__DisplayClass0.CS$<>9__inst.<M>b__1);
        }
    }
}

Ủy quyền thay đổi hành vi bộ nhớ đệm trong Roslyn nói về lý do thực hiện thay đổi này.


2
Cảm ơn, tôi đã tự hỏi tại sao Func của tôi <int> f = () => 5 's phương pháp không tĩnh
vc 74


1

Phương thức không có bao đóng và cũng tham chiếu đến chính phương thức tĩnh (Console.WriteLine), vì vậy tôi mong đợi nó là phương thức tĩnh. Phương thức sẽ khai báo một kiểu ẩn danh bao quanh cho một bao đóng, nhưng trong trường hợp này, nó không bắt buộc.

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.