C#中Delegate和Event的实现原理以及用法。

来源:互联网 发布:迪优美特网络机顶盒x7 编辑:程序博客网 时间:2024/05/21 09:18

最近做项目,发现一些线程方法异常,查半天没有发现什么的地方能引发这些异常。具体看下面列子。


public class A
A a = new A();
A b =a;

object.ReferenceEquals(a, b)是返回true。
如果把a =null; 那么b是不是也是 null?

其实不然,a赋值为null,只是a指向了一个null,b还是指向的 newA();如图所示:






string  mA="abc";
string mB=“abc”;
object.ReferenceEquals(mA, mB);这个函数的结果是 false还是true?

答案是true。

原因是 字符串是不可改变类型?

what?听都没听过上面是不可改变类型。

ok,接下来我们来一起探讨以下上面叫不可改变类型;

imutable ?  what's meaning?(an object is immutable if its state doesn’t change once the object has been created);

意思是对象一旦被创建,则对象的状态不可改变,如字符串, string mA=“abc”;那么 abc一旦被创建,则abc所有状态不可改变,如长度,字符串有的状态都不可改变了,那么 mB=“abc”,实际上两个abc是同一个,所以mA和mB引用同一个对象。


上面讲了不可改变,那跟delegate和event有毛关系.啊?

不急上菜;

Delegates are immutable reference types.

Delegate is not delegate,But delegate is Delegate,请注意d有大小写的区分

看代码:

c# delegate 关键字
( public delegate void TestDelegate(string msg).
 private TestDelegate test;
test.GetType() ==MulticastDelegate);

编译该代码,并查看中间语言,有如下类型:



我们看到delegate声明的变量 会编译产生 MulticastDelegate,而MulticastDelegate是集成Delegate的,这下是不是明白许多了,本身c#只提供delegate关键字给你用。

而且我们还看到delegate生成的中间语言(IL)中自动产生了如下方法:

Invoke()
BeginInvoke()
EndInvoke()

这些方法在Delegate中并没有,why?

最主要的原因在于编译器对delegate生成的类,是有签名的,发现没 实例化一个delegate总是要传入一个方法的引用。

而Delegate是不需要的。

下面这个图显示delegate的操作,加、减运算后的结果,注意一定要看懂 后你就明白大部分了:

p.instanceMethod(string msg)

{

return msg;

}

d1、d2、d3 = new TestDelegate(p.instanceMethod);意思是有d1,d2,d3这三个委托,

[d1, d2, d3] - [d2, d1] =? 问题是 如果 有一个委托是 d1+d2+d3, 另一个是d2+d1,他们相减结果等于多少?参考如下图:



你计算出来[d1, d2, d3] - [d2, d1] =?的结果了吗?

答案是 :[d1,d2,d3],为什么呢,因为在[d1,d2,d3]中找不到有[d2, d1]的出现,c#中delegate实际是用一个列表进行存储的,这个操作如果匹配不到子序列,相减就失败。

ok,委托我们讲解到这里了,接下来谈一谈事件,我们写事件一般会想下面这样写;

private MyEventHandler __MyEvent;
public event MyEventHandler MyEvent
{
    add
    {
        lock (this)
        {
            this.__MyEvent += value;
        }
    }
    remove
    {
        lock (this)
        {
            this.__MyEvent -= value;
        }
    }
}

这样如果不知道细节,看不出来有任何问题,如果在细细想想,可能会发现问题;是因为lock的是this,这是改类的实例,如果其他地方需要等待该对象,或者其他地方也lock了,有可能造成是死锁。

解决方法:用改类的一个object来lock,如object lockObject= new object()。 lock(lockObject)

在调用事件时候我们一般会这样调用:

if (this.MyEvent != null)
{
    this.MyEvent(this, args);
}

这样调用实际上有一个线程安全问题;

if another thread unsubscribes from the event after the if statement but before the event is raised, then this code may result in a NullReferenceException!

这句话的意思是 假如别的线程在 if (this.MyEvent != null)语句执行后, this.MyEvent(this, args)还为执行,其他线程对this.MyEvent作了其他操作如减掉该委托可能等于null,

那么执行  this.MyEvent(this, args)时候可能会引发 空异常。 所以有时候我们看代码 看不出来这里会抛异常,其实如果知道这些细节我们还是能挖掘出来这些问题。

那怎么解决这个问题呢?

有没有想起我们上面提到 delegate是 不可改变类型,这个时候有大用了,不可改变意思是new了后就不可变了, 想 加 减操作实际上是重新 new了新的 对象计算后在赋值的。

如上面的[d1] +[d2] =[d1,d2],这里有三个对象。

只需要这样就可以解决上面的问题;

MyEventHandler myEvent = this.MyEvent;
if (myEvent != null)
{
    myEvent(this, args);
}

this solution does prevent the NullReferenceException race condition; but it introduces another race condition in its place;

这个解决上面的问题,但是他带来了另外一个问题,就是当MyEvent在MyEventHandler myEvent = this.MyEvent执行后改变了,那么myEvent 执行的是改变之前的函数。

不过这种情况 比上面的 要好多了,及时MyEvent在MyEventHandler myEvent = this.MyEvent执行后改变了,我也能保证了 myEvent的执行。


参考文献:
http://csharpindepth.com/Articles/Chapter2/Events.aspx
http://www.codeproject.com/Articles/37474/Threadsafe-Events


第一次写中文章,估计表达的不太清楚,还望批评指正





0 0
原创粉丝点击