2013-07-17 47 views
10

Có ai có thể phân biệt ngắn gọn giữa phương thức ẩn danh và biểu thức lambda không?
Các phương thức ẩn danh so với biểu thức lambda

Sử dụng phương pháp vô danh:

private void DoSomeWork() 
{ 
    if (textBox1.InvokeRequired) 
    { 
     //textBox1.Invoke((Action)(() => textBox1.Text = "test")); 
     textBox1.Invoke((Action)delegate { textBox1.Text = "test"; }); 
    } 
} 

Là nó chỉ một biểu thức lambda bình thường được đúc để một đại biểu mạnh mẽ gõ hoặc có nhiều của nó có mái che.

Tôi cũng biết rằng một đại biểu mạnh mẽ gõ như làm theo

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName) 

đủ như một tham số kiểu System.Delegate, nhưng ý tưởng của phương pháp giấu tên còn khá mới mẻ đối với tôi.

+1

Bạn đã xem cái này chưa? và có thể là những câu hỏi khác? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –

+0

tôi hoàn toàn hiểu được một phương thức vô danh/biểu thức lambda và cách sử dụng nào, câu hỏi của tôi là chúng khác nhau theo bất kỳ phương tiện nào từ ẩn danh đại biểu hoặc họ chỉ là cùng một – sarepta

+2

Vâng, tôi chỉ cần viết một tải văn bản, vì vậy hy vọng bạn sẽ tìm thấy câu trả lời của bạn ở đó, nếu không cho tôi biết :) –

Trả lời

24

Điều gì một phương pháp ẩn danh? Nó thực sự vô danh? Nó có tên không? Tất cả các câu hỏi hay, vì vậy chúng ta hãy bắt đầu với chúng và làm việc theo cách của chúng ta theo các biểu thức lambda khi chúng ta đi cùng.

Khi bạn làm điều này:

public void TestSomething() 
{ 
    Test(delegate { Debug.WriteLine("Test"); }); 
} 

Điều gì thực sự xảy ra?

Trình biên dịch đầu tiên quyết định lấy "cơ thể" của phương pháp, đó là:

Debug.WriteLine("Test"); 

và tách ra rằng vào một phương pháp.

Hai câu hỏi trình biên dịch hiện có để trả lời:

  1. tôi nên đặt phương pháp này ở đâu?
  2. Chữ ký của phương pháp trông như thế nào?

Câu hỏi thứ hai được trả lời dễ dàng. Phần delegate { trả lời. Phương pháp này có không có thông số (không có gì giữa delegate{), và vì chúng ta không quan tâm đến tên của nó (vì thế "vô danh" phần), chúng ta có thể khai báo các phương pháp như vậy:

public void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

Nhưng tại sao nó làm tất cả điều này?

Hãy xem đại biểu, chẳng hạn như Action thực sự là.

Một đại biểu, nếu chúng ta tạm thời bỏ qua thực tế là đại biểu trong đó.NET đang thực sự liên kết danh sách các nhiều single "đại biểu", một tham chiếu (con trỏ) để hai điều:

  1. Một trường hợp đối tượng
  2. Một phương pháp trên mà đối tượng dụ

Như vậy, với kiến thức đó, đoạn mã đầu tiên thực sự có thể được viết lại như thế này:

public void TestSomething() 
{ 
    Test(new Action(this.SomeOddMethod)); 
} 

private void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

Bây giờ, vấn đề với điều này là comp iler không có cách nào để biết những gì Test thực sự làm với các đại biểu nó được đưa ra, và kể từ một nửa của đại biểu là một tham chiếu đến trường hợp mà phương pháp được gọi là, this trong ví dụ trên, chúng tôi không biết bao nhiêu dữ liệu sẽ được tham chiếu.

Ví dụ: hãy xem xét liệu mã trên có phải là một phần của đối tượng thực sự lớn không, nhưng đối tượng chỉ tạm thời hoạt động. Ngoài ra, hãy xem xét rằng Test sẽ lưu trữ đại biểu đó ở nơi nào đó sẽ tồn tại trong một thời gian dài. Đó là "thời gian dài" sẽ tự gắn liền với cuộc sống của đối tượng khổng lồ đó, giữ một tham chiếu đến điều đó trong một thời gian dài là tốt, có lẽ không tốt.

Vì vậy, trình biên dịch không chỉ tạo ra một phương thức, nó còn tạo ra một lớp để giữ nó. Câu trả lời này cho câu hỏi đầu tiên, tôi nên đặt câu hỏi ở đâu?.

Đoạn mã trên do đó có thể được viết lại như sau:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    private void SomeOddMethod() 
    { 
     Debug.WriteLine("Test"); 
    } 
} 

Đó là, ví dụ này, những gì một phương pháp mang tính chất thực sự là tất cả về.

Mọi thứ trở nên lông hơn một chút nếu bạn bắt đầu sử dụng các biến địa phương, hãy xem xét ví dụ sau:

public void Test() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

Đây là những gì xảy ra dưới mui xe, hoặc ít nhất là một cái gì đó rất gần với nó:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    temp.x = 10; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    public int x; 

    private void SomeOddMethod() 
    { 
     Debug.WriteLine("x=" + x); 
    } 
} 

Trình biên dịch tạo một lớp, nâng tất cả các biến mà phương thức yêu cầu vào lớp đó và ghi lại tất cả quyền truy cập vào các biến cục bộ để truy cập vào các trường trên loại ẩn danh.

Tên của lớp, và phương pháp, là một chút kỳ quặc, chúng ta hãy hỏi LINQPad những gì nó sẽ là:

void Main() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

public void Test(Action action) 
{ 
    action(); 
} 

Nếu tôi hỏi LINQPad để sản xuất các IL (Intermediate Language) của chương trình này, tôi có được điều này:

// var temp = new UserQuery+<>c__DisplayClass1(); 
IL_0000: newobj  UserQuery+<>c__DisplayClass1..ctor 
IL_0005: stloc.0  // CS$<>8__locals2 
IL_0006: ldloc.0  // CS$<>8__locals2 

// temp.x = 10; 
IL_0007: ldc.i4.s 0A 
IL_0009: stfld  UserQuery+<>c__DisplayClass1.x 

// var action = new Action(temp.<Main>b__0); 
IL_000E: ldarg.0  
IL_000F: ldloc.0  // CS$<>8__locals2 
IL_0010: ldftn  UserQuery+<>c__DisplayClass1.<Main>b__0 
IL_0016: newobj  System.Action..ctor 

// Test(action); 
IL_001B: call  UserQuery.Test 

Test: 
IL_0000: ldarg.1  
IL_0001: callvirt System.Action.Invoke 
IL_0006: ret   

<>c__DisplayClass1.<Main>b__0: 
IL_0000: ldstr  "x=" 
IL_0005: ldarg.0  
IL_0006: ldfld  UserQuery+<>c__DisplayClass1.x 
IL_000B: box   System.Int32 
IL_0010: call  System.String.Concat 
IL_0015: call  System.Diagnostics.Debug.WriteLine 
IL_001A: ret   

<>c__DisplayClass1..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: ret   

Ở đây bạn có thể thấy rằng tên của lớp là UserQuery+<>c__DisplayClass1, và tên của phương pháp này là <Main>b__0. Tôi đã chỉnh sửa trong mã C# đã tạo mã này, LINQPad không tạo ra bất kỳ thứ gì ngoài IL trong ví dụ trên.

Dấu hiệu ít hơn và lớn hơn ở đó để đảm bảo rằng bạn không thể ngẫu nhiên tạo ra một loại và/hoặc phương thức phù hợp với những gì trình biên dịch tạo ra cho bạn.

Vì vậy, về cơ bản đó là phương pháp ẩn danh.

Vậy đây là gì?

Test(() => Debug.WriteLine("Test")); 

Vâng, trong trường hợp này cũng giống nhau, đó là lối tắt để tạo phương pháp ẩn danh.

Bạn có thể viết này theo hai cách:

() => { ... code here ... } 
() => ... single expression here ... 

Trong hình thức đầu tiên của nó bạn có thể viết tất cả các mã bạn sẽ làm gì trong một cơ thể phương pháp thông thường. Ở dạng thứ hai, bạn được phép viết một biểu thức hoặc câu lệnh.

Tuy nhiên, trong trường hợp này trình biên dịch sẽ đối xử này:

() => ... 

theo cùng một cách như thế này:

delegate { ... } 

Họ vẫn phương pháp mang tính chất nó chỉ là cú pháp () => là một lối tắt để đến đó.

Vì vậy, nếu đó là lối tắt để truy cập vào nó, tại sao chúng tôi có nó?

Vâng, nó giúp cuộc sống dễ dàng hơn một chút với mục đích được thêm vào, đó là LINQ.

xem xét tuyên bố LINQ này:

var customers = from customer in db.Customers 
       where customer.Name == "ACME" 
       select customer.Address; 

Mã này được viết lại như sau:

var customers = 
    db.Customers 
     .Where(customer => customer.Name == "ACME") 
     .Select(customer => customer.Address"); 

Nếu bạn đã sử dụng cú pháp delegate { ... }, bạn sẽ phải viết lại các biểu thức với return ... và do đó và trông họ sôi nổi hơn. Cú pháp lambda do đó được thêm vào để làm cho cuộc sống của chúng ta trở nên dễ dàng hơn khi viết mã như trên.

Vậy biểu thức là gì?

Cho đến nay tôi đã không được hiển thị như thế nào Test đã được xác định, nhưng chúng ta hãy xác định Test cho đoạn code trên:

public void Test(Action action) 

này là đủ. Nó nói rằng "Tôi cần một đại biểu, nó là loại hành động (không có tham số, trả về không có giá trị)".

Tuy nhiên, Microsoft cũng đã thêm một cách khác nhau để xác định phương pháp này:

public void Test(Expression<Func<....>> expr) 

Lưu ý rằng tôi đã đánh rơi một phần ở đó, phần ...., hãy trở lại với .

Mã này, kết hợp với cuộc gọi này:

Test(() => x + 10); 

sẽ không thực sự vượt qua trong một đại biểu, cũng không phải bất cứ điều gì có thể được gọi là (ngay lập tức).Thay vào đó, trình biên dịch sẽ viết lại mã này vào một cái gì đó tương tự (nhưng không phải ở tất cả các loại tương tự) vào mã bên dưới:

var operand1 = new VariableReferenceOperand("x"); 
var operand2 = new ConstantOperand(10); 
var expression = new AdditionOperator(operand1, operand2); 
Test(expression); 

Về cơ bản trình biên dịch sẽ xây dựng một đối tượng Expression<Func<...>>, chứa tham chiếu đến các biến, các giá trị văn chương , các toán tử được sử dụng, v.v. và truyền cây đối tượng đó cho phương thức.

Tại sao?

Vâng, hãy xem xét phần db.Customers.Where(...) ở trên. Nó sẽ không được tốt đẹp nếu, thay vì tải xuống tất cả khách hàng (và tất cả dữ liệu của họ) từ cơ sở dữ liệu cho khách hàng, lặp qua tất cả, tìm ra khách hàng có tên đúng, vv mã thực sự yêu cầu cơ sở dữ liệu tìm thấy khách hàng đơn lẻ, chính xác, đúng lúc đó?

Đó là mục đích đằng sau biểu thức. Entity Framework, Linq2SQL, hoặc bất kỳ lớp cơ sở dữ liệu hỗ trợ LINQ khác, sẽ lấy biểu thức đó, phân tích nó, tách nó ra, và viết lên một SQL được định dạng đúng để được thực hiện đối với cơ sở dữ liệu.

Điều này có thể không bao giờ làm gì nếu chúng tôi vẫn cấp cho đại biểu các phương pháp có chứa IL. Nó chỉ có thể làm điều này vì một vài điều:

  1. Cú pháp cho phép trong một biểu thức lambda thích hợp cho một Expression<Func<...>> được giới hạn (không báo cáo, vv)
  2. Cú pháp lambda không có dấu ngoặc nhọn, mà nói với trình biên dịch rằng đây là một dạng đơn giản của mã

Vì vậy, chúng ta hãy tóm tắt:

  1. Các phương thức ẩn danh thực sự không phải là tất cả những gì ẩn danh, chúng kết thúc như một kiểu được đặt tên, với một phương thức được đặt tên, chỉ bạn không phải tự mình đặt tên cho những thứ đó
  2. Đó là rất nhiều ma thuật trình biên dịch. xung quanh để bạn không cần phải
  3. Expressions và đại biểu hai cách để xem xét một số trong những điều tương tự
  4. Expressions có nghĩa là cho các khuôn khổ mà muốn biết gì mã không và làm thế nào, để họ có thể sử dụng kiến ​​thức đó để tối ưu hóa quá trình (như viết một câu lệnh SQL)
  5. Các đại biểu có nghĩa là khuôn khổ mà chỉ quan tâm đến là có thể gọi phương thức

Chú thích:

  1. Phần .... cho một biểu thức đơn giản như vậy có nghĩa là cho các loại giá trị trả về bạn nhận được từ biểu thức. Chỉ () => ... simple expression ... chỉ cho phép các biểu thức , tức là, cái gì đó trả về một giá trị và nó không thể là nhiều câu lệnh.Như vậy, một kiểu biểu thức hợp lệ là: Expression<Func<int>>, về cơ bản, biểu thức là một hàm (phương thức) trả về một giá trị số nguyên.

    Lưu ý rằng "biểu thức trả về giá trị" là giới hạn cho các thông số hoặc loại Expression<...>, nhưng không phải của đại biểu. Đây là hoàn toàn bộ luật nếu kiểu tham số của Test là một Action:

    Test(() => Debug.WriteLine("Test")); 
    

    Rõ ràng, Debug.WriteLine("Test") không trả lại bất cứ điều gì, nhưng điều này là hợp pháp. Tuy nhiên, nếu phương thức Test yêu cầu một biểu thức thì sẽ không có, vì biểu thức phải trả về giá trị.

+2

Thuật ngữ "ẩn danh" thực sự là rất tốt hạn cho điều này. Xem xét nếu ai đó tặng một tấn tiền cho một lý do chính đáng. Rõ ràng là người đó có một cái tên, nó chỉ không được biết đến với những người nhận tiền. Trong trường hợp này, tên của phương thức này không được bạn biết đến, nhưng thực tế nó có tên. –

+0

Bài diễn văn xuất sắc, nhưng có một sự khác biệt nhỏ ngoài đường cú pháp khi cố gắng xử lý những biểu thức này dưới dạng Biểu thức. Điều gì sẽ xảy ra nếu bạn cố gắng chuyển một đại biểu vô danh vào một phương thức chỉ có một 'Expression >'? –

+0

Bạn không thể làm điều đó. Bạn nhận được một lỗi trình biên dịch, "Một biểu thức phương thức ẩn danh không thể được chuyển đổi thành một cây biểu thức". Điều này là từ 'Test (delegate {return 10;});' khi phương thức được khai báo là 'public void Test (Biểu thức > expr)'. Lưu ý, tôi không nói rằng không có thứ gì tôi chưa từng đề cập đến, vì vậy nếu bạn có thể tìm thấy một số tôi muốn được mở rộng trên những người đó, nhưng chuyển giao một đại biểu đến một phương pháp đòi hỏi một biểu thức là một đi. –

1

Để chính xác, tên bạn gọi là 'đại biểu ẩn danh' thực sự là một phương pháp ẩn danh.

Vâng, cả lambdas và các phương thức vô danh chỉ là đường cú pháp. Trình biên dịch sẽ tạo ra ít nhất một phương thức 'bình thường' cho bạn, mặc dù đôi khi (trong trường hợp đóng) nó sẽ tạo ra một lớp lồng nhau với phương thức không còn nặc danh trong nó.

5

Có một sự khác biệt nhỏ mà bạn cần biết. Hãy xem xét các truy vấn sau đây (sử dụng NorthWind ngôn ngữ).

Customers.Where(delegate(Customers c) { return c.City == "London";}); 
Customers.Where(c => c.City == "London"); 

Người đầu tiên sử dụng một đại biểu ẩn danh và người thứ hai sử dụng biểu thức lambda. Nếu bạn đánh giá kết quả của cả hai bạn sẽ thấy cùng một điều. Tuy nhiên, nhìn vào SQL được tạo ra, chúng ta sẽ thấy một câu chuyện hoàn toàn khác. Là người đầu tiên tạo ra

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 

Trong khi thứ hai tạo ra Thông báo

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 
WHERE [t0].[City] = @p0 

trong trường hợp đầu tiên, mệnh đề where không được đưa vào cơ sở dữ liệu. Tại sao điều này? Trình biên dịch có thể xác định rằng biểu thức lambda là một biểu thức đơn dòng đơn có thể được giữ lại như một cây biểu thức, trong khi đại biểu ẩn danh không phải là một biểu thức lambda và do đó không bao bọc như một Expression<Func<T>>. Kết quả là trong trường hợp đầu tiên, kết hợp tốt nhất cho phương thức Mở rộng ở đâu là phương thức mở rộng IEnumerable thay vì phiên bản IQueryable yêu cầu Expression<Func<T, bool>>.

Tại thời điểm này, có rất ít sử dụng đối với người được ủy quyền ẩn danh. Nó linh hoạt hơn và ít linh hoạt hơn. Nói chung, tôi khuyên bạn nên luôn sử dụng cú pháp lambda trên cú pháp ủy nhiệm ẩn danh và nhận được khả năng phân tích cú pháp và tính tương đồng cú pháp.

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