【转】Emit学习-进阶篇-定义事件

转自http://www.cnblogs.com/yingql/archive/2009/03/28/1423999.html
    之前在研究如何用
Emit为动态类添加事件,本来以为会非常简单,但是却碰到了许多的问题,有些问题在之前的答疑篇中已经提到了,并予以了解决,虽然有些地方自己也不是很明白,但毕竟还是解决了,最后比较我写的IL代码,和系统自动生成的,总有一些地方无法做到一致。特别是在为事件添加addremove方法时,碰到了许多问题,下面我将针对这些问题进行讲解。按照惯例,先给出要实现的类的C#代码,方便反编译后对照着进行IL代码的书写,代码如下:


public class Publisher
{
    
private bool isStart = false;
    
private Random random = new Random(DateTime.Now.Millisecond);

    
public void Start()
    {
        
if (!isStart)
        {
            isStart 
= true;
            GenerateRand();
        }
    }

    
public void Stop()
    {
        isStart 
= false;
    }

    
private void GenerateRand()
    {
        
while (isStart)
        {
            OnRandGenerated(random.Next(
10000));
            Thread.Sleep(
1000);
        }
    }

    
#region Event

    
public event EventHandler<RandGeneratedEventArgs> RandGenerated;

    
protected virtual void OnRandGenerated(int rand)
    {
        RaiseRandGeneratedEvent(rand);
    }

    
private void RaiseRandGeneratedEvent(int rand)
    {
        EventHandler
<RandGeneratedEventArgs> temp = RandGenerated;
        
if (temp != null)
        {
            RandGeneratedEventArgs arg 
= new RandGeneratedEventArgs(rand);
            temp(
this, arg);
        }
    }

    
#endregion      
}

首先,我们定义类中的相关字段及方法,这部分使用之前基础篇中提到过的步骤就可以完成,不再累述,但是要指出的一点是,在类中,以如下形式初始化的字段:private Random random = new Random(DateTime.Now.Millisecond);如果初始化的值不是常量,则需要在无参构造函数中对齐进行初始化,否则可以使用FieldBuilderSetConstant方法进行初始化。

在定义事件时,发现需要手动添加事件的addremove方法,在这里面会用到事件的本身,而OpCodes指令中好像也没有加载事件的指令,只有加载字段的指令,然后仔细研究了自动生成的IL代码,发现其中生成了一个与定义的事件的同名的私有字段,然后相关的访问都是通过对这个字段的访问来进行的,于是豁然开朗,看来Reflector果然是个好东西呀!

当定义事件的addremove方法时,需要注意,我们平时在C#中使用的都是形如+=-=这样的形式,但是我用这种方法后发现行不通,最后又是在Reflector的帮助下,发现应该分别使用DelegateCombineRemove方法,废话不多说,代码和相关注释如下:


#region Event

//定义事件,事件的可访问性由和它相关的getset方法决定
EventBuilder randGeneratedEvent = publisherTypeBuilder.DefineEvent("RandGenerated", EventAttributes.None, eventType);

MethodBuilder addEventBuilder 
= publisherTypeBuilder.DefineMethod("add_RandGenerated", MethodAttributes.Public, nullnew Type[] { eventType });
//注意在我们使用事件时,使用的是+=操作,但在IL代码中并不是如此,应该使用Delegate.Combine方法
ILGenerator addEventIL = addEventBuilder.GetILGenerator();

addEventIL.Emit(OpCodes.Ldarg_0);
addEventIL.Emit(OpCodes.Ldarg_0);
addEventIL.Emit(OpCodes.Ldfld, randGeneratedField);
addEventIL.Emit(OpCodes.Ldarg_1);
addEventIL.Emit(OpCodes.Call, 
typeof(Delegate).GetMethod("Combine"new Type[] { eventType, eventType }));
//返回的是Delegate类型,所以需要进行转换
addEventIL.Emit(OpCodes.Castclass, eventType);
addEventIL.Emit(OpCodes.Stfld, randGeneratedField);

randGeneratedEvent.SetAddOnMethod(addEventBuilder);

MethodBuilder removeEventBuilder 
= publisherTypeBuilder.DefineMethod("remove_RandGenerated", MethodAttributes.Public, nullnew Type[] { eventType });
//注意在我们使用事件时,使用的是-=操作,但在IL代码中并不是如此,应该使用Delegate.Remove方法
ILGenerator removeEventIL = removeEventBuilder.GetILGenerator();

removeEventIL.Emit(OpCodes.Ldarg_0);
removeEventIL.Emit(OpCodes.Ldarg_0);
removeEventIL.Emit(OpCodes.Ldfld, randGeneratedField);
removeEventIL.Emit(OpCodes.Ldarg_1);
removeEventIL.Emit(OpCodes.Call, 
typeof(Delegate).GetMethod("Remove"new Type[] { eventType, eventType }));
//返回的是Delegate类型,所以需要进行转换
removeEventIL.Emit(OpCodes.Castclass, eventType);
removeEventIL.Emit(OpCodes.Stfld, randGeneratedField);

randGeneratedEvent.SetRemoveOnMethod(removeEventBuilder);

#endregion

最后,同样提供完整的源码下载 事件定义

posted @ 2009-08-03 15:19  JoeWang  阅读(121)  评论(0)    收藏  举报