Tại sao biểu thức lambda phải được truyền khi được cung cấp dưới dạng tham số Ủy quyền thuần túy


124

Lấy phương thức System.Windows.Forms.Control.Invoke (Phương thức ủy nhiệm)

Tại sao điều này gây ra lỗi thời gian biên dịch:

string str = "woop";
Invoke(() => this.Text = str);
// Error: Cannot convert lambda expression to type 'System.Delegate'
// because it is not a delegate type

Tuy nhiên, điều này hoạt động tốt:

string str = "woop";
Invoke((Action)(() => this.Text = str));

Khi phương thức yêu cầu một Đại biểu thuần túy?

Câu trả lời:


125

Một biểu thức lambda hoặc có thể được chuyển đổi sang một loại đại biểu hoặc một cây biểu hiện - nhưng nó phải biết loại đại biểu. Chỉ biết chữ ký là không đủ. Ví dụ: giả sử tôi có:

public delegate void Action1();
public delegate void Action2();

...

Delegate x = () => Console.WriteLine("hi");

Bạn mong đợi kiểu cụ thể của đối tượng được gọi xlà gì? Có, trình biên dịch có thể tạo kiểu đại biểu mới với chữ ký thích hợp, nhưng điều đó hiếm khi hữu ích và bạn sẽ có ít cơ hội kiểm tra lỗi hơn.

Nếu bạn muốn gọi dễ dàng Control.Invokebằng Actioncách dễ dàng nhất là thêm một phương thức mở rộng vào Control:

public static void Invoke(this Control control, Action action)
{
    control.Invoke((Delegate) action);
}

1
Cảm ơn - Tôi đã cập nhật câu hỏi vì tôi nghĩ chưa gõ là thuật ngữ sử dụng sai.
xyz

1
Đó là một giải pháp rất thanh lịch và trưởng thành. Tôi có lẽ muốn gọi nó là "InvokeAction" để tên cho thấy những gì chúng ta đang thực sự gọi (thay vì một đại biểu chung chung) nhưng chắc chắn nó làm việc cho tôi :)
Matthias Hryniszak

7
Tôi không đồng ý rằng nó "hiếm khi hữu ích và ...". Trong trường hợp gọi Begin / Invoke bằng lambda, bạn chắc chắn không quan tâm đến việc kiểu đại biểu có được tạo tự động hay không, chúng tôi chỉ muốn thực hiện cuộc gọi. Trong tình huống nào, một phương thức chấp nhận một Đại biểu (loại cơ sở) sẽ quan tâm đến loại cụ thể là gì? Ngoài ra, mục đích của phương pháp mở rộng là gì? Nó không làm cho bất cứ điều gì dễ dàng hơn.
Tergiver

5
Ah! Tôi đã thêm phương thức mở rộng và thử Invoke(()=>DoStuff)vẫn gặp lỗi. Vấn đề là tôi đã sử dụng ẩn 'this'. Để có được nó để làm việc từ bên trong một Thành viên BKS bạn phải rõ ràng: this.Invoke(()=>DoStuff).
Tergiver

2
Đối với bất kỳ ai khác đọc bài này, tôi nghĩ câu hỏi và câu trả lời cho C #: Tự động hóa mẫu mã InvokeRequired rất hữu ích.
Erik Philips

34

Bạn mệt mỏi với việc đúc lambdas nhiều lần?

public sealed class Lambda<T>
{
    public static Func<T, T> Cast = x => x;
}

public class Example
{
    public void Run()
    {
        // Declare
        var c = Lambda<Func<int, string>>.Cast;
        // Use
        var f1 = c(x => x.ToString());
        var f2 = c(x => "Hello!");
        var f3 = c(x => (x + x).ToString());
    }
}

3
Đó là một cách sử dụng tuyệt vời của generic.
Peter Wone 10/11/11

2
Tôi phải thừa nhận rằng, điều này khiến tôi mất một lúc để tìm ra lý do tại sao nó hoạt động. Xuất sắc. Thật tệ là tôi không sử dụng được nó ngay bây giờ.
William

1
Bạn có thể vui lòng giải thích cách sử dụng của cái này? Thật khó để tôi hiểu được điều này? Cảm ơn rất nhiều.
shahkalpesh

Nó đã bao giờ đọc điều này hãy nói điều đó một mình nhưng tôi nghĩ tôi thích câu trả lời này hơn câu trả lời của Jon Skeet!
Pogrindis,

@shahkalpesh nó không phức tạp lắm. Hãy xem nó theo cách này, Lambda<T>lớp có một phương thức chuyển đổi danh tính được gọi Cast, phương thức này trả về bất cứ thứ gì được truyền vào ( Func<T, T>). Bây giờ, hàm Lambda<T>được khai báo Lambda<Func<int, string>>có nghĩa là nếu bạn truyền một phương thức Func<int, string>tới Cast, nó sẽ trả Func<int, string>về, vì Ttrong trường hợp này là như vậy Func<int, string>.
nawfal

12

Chín phần mười thời gian mọi người nhận được điều này vì họ đang cố gắng điều khiển chuỗi giao diện người dùng. Đây là cách lười biếng:

static void UI(Action action) 
{ 
  System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action); 
}

Bây giờ nó được nhập, sự cố đã biến mất (qv Skeet's anwer) và chúng ta có cú pháp rất ngắn gọn sau:

int foo = 5;
public void SomeMethod()
{
  var bar = "a string";
  UI(() =>
  {
    //lifting is marvellous, anything in scope where the lambda
    //expression is defined is available to the asynch code
    someTextBlock.Text = string.Format("{0} = {1}", foo, bar);        
  });
}

Để có điểm thưởng, đây là một mẹo khác. Bạn sẽ không làm điều này cho nội dung giao diện người dùng nhưng trong trường hợp bạn cần SomeMethod để chặn cho đến khi nó hoàn thành (ví dụ: I / O yêu cầu / phản hồi, chờ phản hồi), hãy sử dụng WaitHandle (qv msdn WaitAll, WaitAny, WaitOne).

Lưu ý rằng AutoResetEvent là một dẫn xuất WaitHandle.

public void BlockingMethod()
{
  AutoResetEvent are = new AutoResetEvent(false);
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    are.Set();
  });      
  are.WaitOne(); //don't exit till asynch stuff finishes
}

Và một mẹo cuối cùng vì mọi thứ có thể trở nên rối rắm: WaitHandles làm ngưng trệ chuỗi. Đây là những gì họ phải làm. Nếu bạn cố gắng điều chỉnh chuỗi giao diện người dùng trong khi nó bị treo, ứng dụng của bạn sẽ bị treo. Trong trường hợp này (a) một số quá trình tái cấu trúc nghiêm trọng đang được thực hiện và (b) là một bản hack tạm thời, bạn có thể đợi như sau:

  bool wait = true;
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    wait = false;
  });
  while (wait) Thread.Sleep(100);

3
Tôi thấy thật thú vị khi mọi người phải bỏ phiếu cho một câu trả lời chỉ vì cá nhân họ không thấy nó hấp dẫn. Nếu nó sai và bạn biết điều này, thì hãy nói nó sai. Nếu bạn không thể làm như vậy, thì bạn không có cơ sở để phản đối. Nếu nó epically sai rồi nói điều gì đó như "baloney Xem [trả lời đúng]" hoặc có thể "Không phải là một giải pháp đề nghị, xem [những thứ tốt hơn]"
Peter Wone

1
Vâng, tôi là người phụ trách thẳng thắn; nhưng dù sao tôi cũng không biết tại sao nó lại bị bỏ phiếu; mặc dù tôi chưa sử dụng mã thực tế, nhưng tôi nghĩ đây là một phần giới thiệu nhanh về cách gọi đa luồng giao diện người dùng và nó có một số thứ mà tôi chưa thực sự nghĩ đến như vậy, chắc chắn +1 để vượt lên trên và hơn thế nữa. :) Ý tôi là, bạn đã đưa ra một phương pháp nhanh chóng hay để thực hiện các lệnh gọi đại biểu; bạn cung cấp một tùy chọn cho các cuộc gọi phải chờ; và bạn theo dõi nó với một cách nhanh chóng tốt đẹp để những người bị mắc kẹt trong Địa ngục chuỗi giao diện người dùng để lấy lại một chút quyền kiểm soát. Câu trả lời tốt, tôi cũng sẽ nói + <3. :)
shelleybutterfly

System.Windows.Threading.Dispatcher.CurrentDispatchersẽ trả về điều phối của luồng CURRENT - tức là nếu bạn gọi phương thức này từ một luồng không phải là luồng giao diện người dùng, mã sẽ không được chạy trên luồng giao diện người dùng.
BrainSlugs83

@ BrainSlugs83 điểm tốt, có lẽ điều tốt nhất là một ứng dụng có thể ghi lại tham chiếu đến trình điều phối luồng giao diện người dùng và đặt nó ở nơi nào đó có thể truy cập được trên toàn cầu. Tôi ngạc nhiên vì đã lâu như vậy mới có người nhận ra điều đó!
Peter Wone

4

Peter Wone. bạn là da man. Nói xa hơn về khái niệm của bạn, tôi đã đưa ra hai hàm này.

private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}

Tôi đặt hai chức năng này vào ứng dụng Biểu mẫu của mình và tôi có thể thực hiện cuộc gọi từ các nhân viên nền như thế này

int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));

Có thể hơi lười biếng, nhưng tôi không phải thiết lập các chức năng do worker done, điều này rất tiện dụng trong những trường hợp như thế này

private void Ping_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
  int count = this.dg.Rows.Count;
  System.Threading.Tasks.Parallel.For(0, count, i => 
  {
    string ip = UIF<string>(() => this.GetIp(i));
    bool r = GoPingIt(ip);
    UIA(() => this.SetPing(i, r));
  });
  UIA(() => SetAllControlsEnabled(true));
}

Về cơ bản, lấy một số địa chỉ ip từ một DataGridView gui, ping chúng, đặt các biểu tượng kết quả thành màu xanh lá cây hoặc màu đỏ và kích hoạt lại các nút trên biểu mẫu. Có, nó là "song song.cho" trong một công việc nền. Vâng, đó là RẤT NHIỀU yêu cầu chi phí, nhưng nó không đáng kể đối với danh sách ngắn và mã nhỏ gọn hơn nhiều.


1

Tôi đã cố gắng xây dựng điều này dựa trên câu trả lời của @Andrey Naumov . Có thể đây là một cải tiến nhỏ.

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

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

    public Func<S, T> Func<T>(Func<S, T> func)
    {
        return func;
    }

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

Trong đó tham số kiểu Slà tham số chính thức (tham số đầu vào, bắt buộc tối thiểu để suy ra phần còn lại của các kiểu). Bây giờ bạn có thể gọi nó như sau:

var l = new Lambda<int>();
var d1 = l.Func(x => x.ToString());
var e1 = l.Expression(x => "Hello!");
var d2 = l.Func(x => x + x);

//or if you have only one lambda, consider a static overload
var e2 = Lambda<int>.CreateExpression(x => "Hello!");

Bạn có thể có thêm quá tải cho Action<S>Expression<Action<S>>tương tự trong cùng một lớp. Đối với khác được xây dựng trong đại biểu và biểu hiện các loại, bạn sẽ phải viết các lớp riêng biệt như Lambda, Lambda<S, T>, Lambda<S, T, U>, vv

Lợi thế của điều này mà tôi thấy so với cách tiếp cận ban đầu:

  1. Một đặc tả kiểu nhỏ hơn (chỉ tham số chính thức cần được chỉ định).

  2. Điều này cho phép bạn tự do sử dụng nó để chống lại bất kỳ Func<int, T>, không chỉ khi Tđược nói string, như được hiển thị trong các ví dụ.

  3. Hỗ trợ các biểu thức ngay lập tức. Trong cách tiếp cận trước đó, bạn sẽ phải chỉ định lại các loại, như:

    var e = Lambda<Expression<Func<int, string>>>.Cast(x => "Hello!");
    
    //or in case 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Func<int, string>>>(x => "Hello!");

    cho các biểu thức.

  4. Việc mở rộng lớp cho các kiểu đại biểu (và biểu thức) khác cũng tương tự như trên.

    var e = Lambda<Action<int>>.Cast(x => x.ToString());
    
    //or for Expression<Action<T>> if 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Action<int>>>(x => x.ToString());

Theo cách tiếp cận của tôi, bạn chỉ phải khai báo các kiểu một lần (ít hơn một lần đối với Funcs).


Một cách khác để triển khai câu trả lời của Andrey giống như không hoàn toàn chung chung

public sealed class Lambda<T>
{
    public static Func<Func<T, object>, Func<T, object>> Func = x => x;
    public static Func<Expression<Func<T, object>>, Expression<Func<T, object>>> Expression = x => x;
}

Vì vậy, mọi thứ giảm xuống:

var l = Lambda<int>.Expression;
var e1 = l(x => x.ToString());
var e2 = l(x => "Hello!");
var e3 = l(x => x + x);

Điều đó thậm chí còn ít hơn khi gõ, nhưng bạn sẽ mất đi sự an toàn khi gõ và imo, điều này không đáng.


1

Đến bữa tiệc hơi muộn nhưng bạn cũng có thể truyền như thế này

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});


0

Chơi với XUnit và Xác định thông thạo tôi có thể sử dụng khả năng nội tuyến này theo cách mà tôi thấy rất hay.

Trước

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    Action action = () => {
        using (var c = DbProviderFactories.GetFactory("MySql.Data.MySqlClient").CreateConnection())
        {
            c.ConnectionString = "<xxx>";
            c.Open();
        }
    };

    action.Should().Throw<Exception>().WithMessage("xxx");
}

Sau

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    ((Action)(() => {
        using (var c = DbProviderFactories.GetFactory("<provider>").CreateConnection())
        {
            c.ConnectionString = "<connection>";
            c.Open();
        }
    })).Should().Throw<Exception>().WithMessage("Unable to find the requested .Net Framework Data Provider.  It may not be installed.");
}
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.