现在的位置: 首页 > 综合 > 正文

【C#语言基础】–委托(Delegate)(一)

2012年02月24日 ⁄ 综合 ⁄ 共 4596字 ⁄ 字号 评论关闭

一.动机

  在Winform程序中我们经常有这样一种需求,就是在一个主窗口中,当用户点击添加按钮时弹出一个子窗口,用户输入数据,最后点击保存或者关闭窗口后需要主窗口中列表能够刷新,以显示出刚刚添加的数据,或者一些传递参数,改变某个控件的值等等。当然,完全可以使用暴露属性的方式来完成,但是扩展性比较好的做法是利用事件。在子窗体中发布一个事件,在父窗体中订阅这个事件,当子窗体保存数据后触发这个事件以修改父窗体中的某个控件的值,代码如下:
  1:定义委托与委托参数

View Code

/// <summary>
/// 窗体间传数据的委托
/// </summary>
/// <param name="sender">event规范要求(这里无用途)</param>
/// <param name="e">传递的参数</param>
public delegate void DataTransmissionHandler(object sender, DataTransmissionArgs e);
/// <summary>
/// 窗体间传数据的委托(泛型)
/// </summary>
/// <param name="sender">event规范要求(这里无用途)</param>
/// <param name="e">传递的参数</param>
public delegate void DataTransmissionHandler<T>(object sender, T e) where T : EventArgs, new();

/// <summary>
/// 参数类(字典对象)
/// </summary>
public class DataTransmissionArgs:EventArgs
{
private IDictionary<object, object> dic;
public IDictionary<object,object> Dic
{
get { return dic; }
set { dic = value; }
}
public DataTransmissionArgs()
{
}
public DataTransmissionArgs(IDictionary<object ,object > innerDictionary)
{
dic
= innerDictionary;
}
}

  2:在子窗体中声明事件,这里为Form2.我们就使用泛型的版本,泛型的版本允许将第二个参数 eventArgs从System.EventArgs继承,这样就可以在多个位置使用相同的委托数据类型,并在支持多个不同参数类型的同时保持强类型。

    private event DataTransmissionHandler<DataTransmissionArgs> myDataHandler;
/// <summary>
/// 数据传递的事件对象,用于通知
/// </summary>
public DataTransmissionHandler<DataTransmissionArgs> MyDataHandler
{
get { return myDataHandler; }
set { myDataHandler = value; }
}

  3:在主窗体Form1中,我们在打开Form2窗体之前订阅了Form2的这个事件

      Form2 form2=new Form2();
form2.MyDataHandler
+= Form1_DataTransmission;
form2.Show();

    
/// <summary>
/// Form1中的回调函数(代理),Form2通过他来完成对Form1中的控件的值的修改
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_DataTransmission(object sender, DataTransmissionArgs e)
{
IDictionary
<object, object> dic = e.Dic;
label1.Text
= dic.Count.ToString();
}

  4: 在子窗体Form2 保存或者关闭时触发之前的事件,最终将调用回调函数Form1_DataTransmission

   private void button2_Click(object sender, EventArgs e)
{
Dictionary
<object,object > dic=new Dictionary<object, object>();
dic.Add(
"aa", "bb");
dic.Add(
"cc", "dd");
DataTransmissionArgs args
=new DataTransmissionArgs(dic);
MyDataHandler(
this, args);//触发事件
}

二.原理

  1:简单委托

  上面的例子中,主要涉及到了2个概念,委托和事件,这一部分主要是从委托开始,它是事件的基础。
  说到委托,大家都会想到什么?delegate?回调?类型安全的函数指针?引用MSDN中的原话,“delegate 是一种可用于封装命名或匿名方法的引用类型。 委托类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。”这句话读起来很抽象,其实关于委托,还有非常多的概念在后面等着我们,下面我们就先看看delegate关键字的背后存在的秘密。为了让问题更清晰一些,我们换一个例子,这个例子也进一步说明了委托的便捷之处,代码来自 C#本质论。

View Code

/// <summary>
/// 比较2个数的大小的委托
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public delegate bool CompareHandler(int first, int second);

class Program
{

static void Main(string[] args)
{
int[] items = { 1, 2, 26, 32, 13, 5, 6, 9, 12 };
Console.WriteLine(
"按数字大小排序:");
BubbleSort(items, GraterThan);
foreach (int item in items)
{
Console.WriteLine(item);
}
Console.WriteLine(
"按字符方式排序:");
BubbleSort(items, AlphabeticalGraterThan);
foreach (int item in items)
{
Console.WriteLine(item);
}
Console.Read();
}
public static void BubbleSort(int[] items, CompareHandler handler)
{
for (int i = items.Length - 1; i >= 0; i--)
{
for (int j = 1; j <= i; j++)
{

if (handler(items[j - 1], items[j]))
{
int tmp = items[j - 1];
items[j
- 1] = items[j];
items[j]
= tmp;
}

}
}
}
/// <summary>
/// 按数字大小排序
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static bool GraterThan(int first, int second)
{
return first > second;
}
/// <summary>
/// 按字符方式排序
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static bool AlphabeticalGraterThan(int first, int second)
{
return first.ToString().CompareTo(second.ToString()) > 0;
}

}

  代码很简单,声明了一个比较2个数字大小的委托,他通过不同的回调函数来实现了不同的比较行为。结果运行如下

  

  我们打开IL反汇编程序看看在CLR中,委托到底是什么摸样

  

  从IL中看出程序中的CompareHandler被编译成了一个类,并且继承了System.MulticastDelegate这个类(MulticastDelegate派生自Delegate),里面包含了4个方法
  第一个为构造器 CompareHandler(Object object, IntPtr method)
  第二个 virtual Invoke(int value)
  第三个 virtual BeginInvoke(int value,AsyncCallback callback,Object obj)
  第四个virtual EndInvoke(IasyncResult result)
  这里重点看一下构造函数和Invoke方法,后面2个主要是涉及到异步委托的问题,将会在后面讨论到。
  在内部:
  因为派生自MulticastDelegate,他其中包含了几个重要的非公有字段。
  _target: 委托对象中为非静态方法时保存回调方法的对象,静态方法则为null
  _methodPtr: 回调方法的标识
  _invocationList:如果为委托链是存储委托数组
  如下图 

  构造函数主要有一个类型为object的参数和一个IntPtr型的methoInfo,从名字可以看出,第一个参数object接收的是委托的回调函数所在那个对象中,例子中如果GraterThan 不是静态的,就是Program的对象,如果是静态的,则为null。第二个参数则是回调函数的引用。构造函数还会做一些其他的处理和初始化,根据委托的类型给invocationList赋值等等。
  在外部:
  我们知道的Target和Method属性,这2个属性的内部字段这里不用说大家也能猜到,需要说明的是,这个时候Method的类型是MethodInfo,这个也是在内部做了些许转换吧。有了这2个属性,我们又可以做一些检查,比如你可以限制这个委托调用的回调函数必须是某个类的对象的函数,还可以检查回调函数的名称(虽然可能意义不是很大)
  Invoke函数:
在上面的代码中,在我们使用handler(items[j - 1], items[j]) 的时候,编译在内部调用了Invoke方法,这里是查看IL可以得出的结论,在Invoke方法内部,他取得_target和_methodPtr的值,然后调用被构造函数包装好了的某个对象上的回调函数。

  

  最后,Invoke方法的签名跟我们的委托的签名是一致的。所以,上面的代码可以被替换为if(handler!=null)
handler.Invoke(items[j - 1], items[j]); 这里的是否为null的判断是必须的

抱歉!评论已关闭.