天天看點

無法将 匿名方法 轉換為類型“System.Delegate”,因為它不是委托類型

程式設計環境要求:VS2008/FX2.0

衆所周知,從VS2005/FX2.0起,在多線程環境下是不允許跨線程修改主線程上視窗控件的。

例如:

private   void  button1_Click( object  sender, EventArgs e)

{

    Thread t  =   new  Thread( new  ThreadStart(CrossThreadCall));

    t.Start();

}

public   void  CrossThreadCall()

{

    Text  =   " test " ;

}

将直接導緻異常:

未處理 System.InvalidOperationException

  Message="線程間操作無效: 從不是建立控件“Form1”的線程通路它。"

  Source="System.Windows.Forms"

  StackTrace:

       在 System.Windows.Forms.Control.get_Handle()

       在 System.Windows.Forms.Control.set_WindowText(String value)

       在 System.Windows.Forms.Form.set_WindowText(String value)

       在 System.Windows.Forms.Control.set_Text(String value)

       在 System.Windows.Forms.Form.set_Text(String value)

       在 delegate_test1.Form1.CrossThreadCall() 位置 f:/app/delegate_test1/delegate_test1/Form1.cs:行号 26

       在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)

       在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

       在 System.Threading.ThreadHelper.ThreadStart()

通用的解決方法是使用Control.Invoke方法來調用一個Delegate,進而安全地跨線程調用。

例如:

public   void  CrossThreadCall()

{

    Invoke( new  void_d(CrossThreadCall_Local));

}

void  CrossThreadCall_Local()

{

    Text  =   " test " ;

}

public   delegate   void  void_d();

但是這樣的缺點是要不得不為每個調用編寫一個Invoke跳闆,還要額外聲明一個委托類型,實在不怎麼優雅。

于是我們想到用匿名函數來寫。我的第一反應是:

Invoke( delegate  { Text  =   " test " ; });

可惜不行。編譯壓根就沒通過,寫着:

無法将 匿名方法 轉換為類型“System.Delegate”,因為它不是委托類型

無語,delegate竟然不是委托類型?

等我把Google翻了一遍後,找到了答案。

The problem the user is seeing is that the Thread ctor accepts a specific delegate -- the ThreadStart delegate.  The C# compiler will check and make sure your anonymous method matches the signature of the ThreadStart delegate and, if so, produces the proper code under-the-covers to create the ThreadStart delegate for you.

But Control.Invoke is typed as accepting a "Delegate".  This means it can accept any delegate-derived type.  The example above shows an anonymous method that has a void return type and takes no parameters.  It's possible to have a number of delegate-derived types that match that signature (such as MethodInvoker and ThreadStart -- just as an example).  Which specific delegate should the C# compiler use?  There's no way for it to infer the exact delegate type so the compiler complains with an error.

也就是說,對于Thread.ctor()來說,由于接受的是一個ThreadStart委托,編譯器便可以将匿名函數與ThreadStart委托類型比對,最後能夠正确編譯。

而對于Control.Invoke()來說,任何的代理類型都是可接受的,也就是說ThreadStart和MethodInvoker都是可以接受的類型。這樣編譯器反而不知道應該用哪個代理去比對匿名函數了,導緻了編譯錯誤的發生。

知道了原因,問題就很容易解決了。我們隻需要加上MethodInvoker這個wrapper就能使用匿名函數了。

Invoke( new  MethodInvoker( delegate  { Text  =   " test " ; }));

或者更簡單地,用Lambda表達式來解決問題:

Invoke( new  MethodInvoker(()  =>  Text  =   " test " ));

希望本文能夠幫助有同樣困惑的朋友。

繼續閱讀