很多人都知道在.Net中string是一个特殊的引用类型,特殊之处之一就是字符串的不变性(immutability)
一个字符一旦被创建就是不可变的,之后对该字符串所进行的一切改变字符串值的操作都会创建一个新的字符串出来
string s1 = "aaaaaa";
s1 = s1.Substring(0, 3);//原字符串“aaaaaa”并没有改变,而是创建了一个新的字符串aaa
在.Net中还有个引用类型,也具有相似的特性,那就是委托Delegate
对于下面的代码,控制台会输出什么?
using System;
using System.Collections.Generic;
using System.Text;
namespace Immutability
{
public delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
MyDelegate d1 = new MyDelegate(foo1);
d1 += foo2;
MyDelegate d2 = d1;
d2 += foo3;
d1();
Console.WriteLine("===========");
d2();
}
static void foo1()
{
Console.WriteLine("foo1");
}
static void foo2()
{
Console.WriteLine("foo2");
}
static void foo3()
{
Console.WriteLine("foo3");
}
}
}
按照一般的理解,d1()和d2()的调用应该在控制台输出同样的内容,可是并非如此,下面是控制台输出的内容
Delegate做为一个引用类型,在执行了d2=d1的语句之后,d1和d2都指向了同一个对象,确实是这样的,变化发生在d2 += foo3;
对于委托的+=操作符,实际在执行时是调用了Delegate类的静态方法public static Delegate Combine(Delegate a, Delegate b),所以d2+=foo3这样的语
句编译之后等价于(MyDelegate) Delegate.Combine(d2, new MyDelegate(Program.foo3));
对于Delegate.Combine方法MSDN的说明是
参数
a:最先出现其调用列表的委托。
b:最后出现其调用列表的委托。
返回值
新的委托,它的调用列表将 a 和 b 的调用列表按该顺序连接在一起。如果 b 为 空引用(在 Visual Basic 中为 Nothing),则返回 a,如果 a 为空引用,则返回 b,如果 a 和 b 均为空引用,则返回空引用。
注意对于返回值的说明,“新的委托”,Delegate.Combine方法会返回一个新的对象,该对象中维护了要调用方法的委托链,所以在执行了d2+=foo3之后
d2已经指向了另一个对象。最终导致了d1()和d2()在控制台中输出不同的内容。
对于委托的-=操作,编译器则会编译为调用Delegate的静态方法public static Delegate Remove(Delegate source, Delegate value),对于该方法,MSDN的说明是
参数
source
类型:System.Delegate
委托,将从中移除 value 的调用列表。
value
类型:System.Delegate
委托,它提供将从其中移除 source 的调用列表的调用列表。
返回值
类型:System.Delegate
一个新委托,其调用列表的构成方法为:获取 source 的调用列表,如果在 source 的调用列表中找到了 value 的调用列表,则从中移除 value 的最后一个调用列表。 如果 value 为 null,或在 source 的调用列表中没有找到 value 的调用列表,则返回 source。 如果 value 的调用列表等于 source 的调用列表,或 source 为空引用,则返回空引用。
与+=操作一样,在执行了-=操作后,也会返回一个新对象。
那么利用委托的这种特性,我们可以做一些事情,一个例子就是“安全地”触发一个事件
对于触发事件,我们一般都是这样写的
public Delegate void MyEventHandler();//定义委托
class ClassA
{
public event MyDelegate MyEvent;//定义事件
protected virtual void OnMyEvent()
{
if(MyEvent!=null)//判断是否有对象订阅了事件MyEvent
{
MyEvent();
}
}
}
这种写法在单线程的程序中没有任何问题,但是在多线程程序中,则可能会产生NullReferenceException。
因为客户端程序可以随时使用+=订阅事件,也可以随时使用-=退订事件。在多线程环境中,程序有可能会在判断完if(MyEvent!=null)之后去执行其他的
线程,而在其他的线程中客户端退订了事件,导致MyEvent成为了Null,此时程序继续执行MyEvent()的话,就会引发NullReferenceException。
微软推荐以下面的方式避免NullReferenceException的出现,我们只需要将OnMyEvent方法最一点改动
protected virtual void OnMyEvent()
{
MyEventHandler eventHandler=MyEvent;//在方法内部创建一个MyEventHandler的引用,使其指向MyEvent引用的对象,
//这样MyEvent发生的变化就不会影响到eventHandler
if(MyEvent!=null)//判断是否有对象订阅了事件MyEvent
{
eventHandler();
}
}
这是我的第一篇博客,能写出这篇博客感谢喜乐的ASP.NET(Alex Song),参考了他的文章
http://www.cnblogs.com/multiplesoftware/archive/2011/12/21/2295386.html