承接上篇,針對讀者提出的關於委托與函數指標、接口回調的問題,筆者闡述觀點並進一步展示委托陣列、多路廣播及跨線程操控等進階用法。
贊同 feiyang 大神的觀點:C++ 類內部方法實質為函數指標,不佔用類記憶體。在 C# 中聲明委托實例並掛載其他類的公共方法,相當於在本類內部擁有該方法的函數指標(存儲於委托對象內)。透過委托對象調用方法時,編譯器將其視為本類聲明的方法(從安全角度論證可行)。委托並非真正的類方法,而是目標方法的代理,這正是「委托/代理」一詞的由來。
嘗試用接口回調模擬控件事件機制,代碼如下:
// 控件端
namespace InfceCalbckEvCtrl
{
public partial class UserControl1 : UserControl
{
public interface ICallbackEvn { void ShowObjTxt(string Txt); }
public ICallbackEvn ObjCallEvn;
private void button1_Click(object sender, EventArgs e)
{
ObjCallEvn.ShowObjTxt("接口回調仿真事件調用成功!");
}
}
}
// 上層調用端
namespace InCallDemo
{
public partial class Form1 : Form, InfceCalbckEvCtrl.UserControl1.ICallbackEvn
{
private void Form1_Load(object sender, EventArgs e)
{
userControl11.ObjCallEvn = new Form1(); // 關鍵點1:必須繼承接口
}
public void ShowObjTxt(string Txt) { MessageBox.Show(Txt); }
}
}
接口回調存在兩大限制:
相比之下,委托更靈活,使用者也更多。
委托陣列展現「對象與方法兼備」特性:
delegate double CompuFun(double parama, double paramb);
class Program
{
static void Main(string[] args)
{
CompuFun[] computefun = new CompuFun[2];
computefun[0] = new CompuFun(DeleMathFuns.AddFun);
computefun[1] = new CompuFun(DeleMathFuns.MultiplyFun);
for (int i = 0; i < 2; i++)
{
Console.WriteLine("調用方法[{0}]:", i);
Console.WriteLine("所得結果:{0}", computefun[i](5, 5));
}
Console.ReadLine();
}
}
class DeleMathFuns
{
public static double AddFun(double a, double b) { return a + b; }
public static double MultiplyFun(double a, double b) { return a * b; }
}
可隨時調用委托對象掛載的方法,並按普通陣列管理多個委托,程序條理清晰。
多個方法掛接至同一委托對象,調用時按掛接順序依次執行:
delegate void CompuFun(double parama, double paramb);
class Program
{
static void Main(string[] args)
{
CompuFun computefun = null;
computefun = new CompuFun(DeleMathFuns.AddFun);
computefun += new CompuFun(DeleMathFuns.MultiplyFun);
computefun(5, 5);
Console.ReadLine();
}
}
class DeleMathFuns
{
public static void AddFun(double a, double b) { Console.WriteLine("AddFun方法調用結果為{0}", a + b); }
public static void MultiplyFun(double a, double b) { Console.WriteLine("MultiplyFun方法調用結果為{0}", a * b); }
}
注意:多路廣播委托的方法返回值必須為 void,否則返回值無法分配。
C# 不支援直接跨線程操作控件,否則拋出異常。兩種解決方案:
Control.CheckForIllegalCrossThreadCalls = false(簡單但不安全,不推薦)。Control.Invoke()(安全標準做法)。核心代碼:
private void SetText()
{
string text = "這個控件的內容是由子線程實現的";
if (this.textBox1.InvokeRequired)
{
TreadDelgate Objdelegate = new TreadDelgate(SetText);
this.Invoke(Objdelegate, new object[] {});
}
else
{
this.textBox1.Text = text;
}
}
深度解析:InvokeRequired 為真時,說明當前方法被視為子線程方法。透過委托在主線程再次調用同一方法,此時方法隸屬主線程,InvokeRequired 為假,直接更新 UI。這證明了「委托實例兼具對象與方法特質」是關鍵:委托作為對象在主線程建立,掛載方法後調用,使方法以主線程身份執行。
同樣邏輯適用於 [delegate].Invoke() 與 Control.Invoke():同步強制單線程執行,異步強制多線程執行,與當前環境是否多線程無關。
程序效果單一,實現方式多樣。筆者提供代表性實例旨在幫助理解,每個人看待事物的方式不同,他人思想未必適合自己。願這系列文章能助你深入認識委托,總結出屬於自己的使用場景。