博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

学习委托(3)-----解析委托的实现机制续篇

Posted on 2007-08-31 09:42  李玉锋  阅读(1128)  评论(5编辑  收藏  举报

来园子看看自己的博客,至今方体会到白驹过隙的含义,瞅着晾在这里的那几篇可怜的蝇头小文,却已然为时间的见证了。想想以前立下的壮志雄心,要做到学习不止,笔端也要勤耕耘,可现今自己博客的境况表现,哈哈!自己确实该怀揣点羞耻之心了,尽管可以找一大堆搪塞的理由。所以今天来涂鸦一篇小文,以慰吾心!

本文乃是《学习委托(3)-----解析委托的实现机制》的延伸或扩充吧,前篇未曾详谈到的关于多播委托的内容在此和各位初学的朋友一起学习一番,当然本人水平有限,高手若有幸光临,尽可拍砖便是。进入正题,同样,为便于讨论,把前篇文章中的简单代码稍加修改写在此处:

    class Program
    
{  
        
static void Main(string
[] args)
        
{
            goodfriend.middleSay allSay
=null
;
            allSay 
+= new
 goodfriend.middleSay(liyufeng.mySay);
            allSay 
+= new
 goodfriend.middleSay(liyufeng.mySecondSay);
            goodfriend.friendSay(allSay);
            allSay 
-= new
 goodfriend.middleSay(liyufeng.mySecondSay);
            goodfriend.friendSay(allSay);
            System.Console.Read();
        }

    }

    
class liyufeng
    
{
        
public static void
 mySay()
        
{
            System.Console.WriteLine(
"I Like You"
);
        }

        
public static void mySecondSay()
        
{
            System.Console.WriteLine(
"Love for ever"
);
        }


    }

    
class goodfriend
    
{
        
public delegate void
 middleSay();
        
public static void
 friendSay(middleSay say)
        
{           
            say();
        }

    }

前面的文章中已经谈到了多播委托,即一个委托对象可以挂接多个方法,如下面的代码:

            goodfriend.middleSay allSay=null;
            allSay 
+= new
 goodfriend.middleSay(liyufeng.mySay);
            allSay 
+= new goodfriend.middleSay(liyufeng.mySecondSay);

嘿嘿!看到上面第一句代码可能有初学的朋友会有疑问喽,goodfriend里的midlleSay委托类型并不是其静态成员,那为什么此处可以直接通过类名的方式进行访问呢?不要被public delegate void middleSay()这句代码的表面现象迷惑了,以为这里仅是声明了一个普通的类型,其实通过这句,编译器会为我们声明一个继承自System.MulticastDelegate的类,那么就是说,委托类型middleSay是goodfriend的一个嵌套类(有的文章也叫内部类),这再一次证明了在前面的文章中所介绍到过的委托在.NET中是一个类;明白了这点,上面的疑问也就可以消散了。紧接着的两句代码实现了多播委托的挂接,运算符“+=”在此处功劳可不小,先从表面上认识一下,这样理解应该可以:把运算符右侧用来包装方法的委托对象添加到左侧的委托对象中,并且再赋给左侧的委托对象;此处就是allSay了。所以通过这里两行代码,方法mySay和mySecondSay都已经挂接到委托allSay上了有过C++编程经验的朋友对这里“+=”的用法应该很熟悉,心中肯定会想这是所谓的运算符重载;其实这里的“+=”只不过是为了语法简洁,C#编译器演绎的一个把戏而已,并不是实在的运算符重载(当然,C#是支持运算符重载的,有兴趣的朋友可以通过Ildasm工具看一下Delegate或是MulticastDelegate类,其中并未有对“+=”的重载);编译器在这里会直接把“+=”转化为对静态方法Combine的调用,下面通过反编译工具Reflector(用微软自带的Ildasm也可以)得到的allSay += new goodfriend.middleSay(liyufeng.mySay)的IL代码(为便于理解此处省略掉了一些代码):

 L_0010: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)

从IL代码可以看出确实是对Combine方法进行了调用,有朋友可能又要问了,前面提到我们的委托类型middleSay是继承自System.MulticastDelegate,为何此处调用的是System.DelegateCombine方法;这点前篇文章中谈到过,MulticastDelegate是继承自Delegate的,而在Delegate类中并未对Combine方法进行重写,所以调用父类Delegate或是子类MulticastDelegate的Combine方法,效果是一样的。我们再来看一下Combine方法都做了些什么,直接看通过得到的源代码了:

public static Delegate Combine(Delegate a, Delegate b)
{
    
if (a == null
)
    
{
        
return b;//直接返回委托对象,上面我们挂接第一个方法的时候,到这里应该就返回了

    }

    
if (b == null)
    
{
        
return
 a;
    }

    
if (!InternalEqualTypes(a, b))
    
{
        
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"
));
    }

    
return a.CombineImpl(b); //挂接两个或两个以上方法的时候,调用这里的CombineImpl方法
}


这里关键就是CombineImpl方法了,它是一个虚方法,在MulticastDelegate类中进行了重新定义,所以到最后我们调用的是MulticastDelegate类中的CombineImpl方法,在CombineImpl方法中才真正实现了委托方法链的挂接操作。同样我们可以方便的用“-=”操作符从委托方法链中移除一个方法,正如allSay -= new goodfriend.middleSay(liyufeng.mySecondSay)所示;“+=”操作符类似,C#编译器会把“-=”转化为对静态法Remove的调用。当然,我们可以在代码中直接调用Combine和Remove方法,而不用C#编译器提供的操作符,来实现对委托方法链的操作

 

通过上面的分析,我们可以了解到,作为基类的Delegate提供了CombineImpl等一系列的虚方法,从而子类型可以重新实现这些虚方法,只是到目前为止,微软并未从再从Delegate类继承一个独立的类型来支持单播委托,想是微软开发小组认为没有必要,而是单播与多播委托都由同一个类MulticastDelegate来支持了,如果样的话,感觉MulticastDelegate类和Delegate类完全可以合并为一个类了;当然也需微软的工程师牛人们以可能会研发出特殊功能的委托类型,从而保留Delegate类以便扩展。

 

   最后再看一下关于委托方法的调用say(),很简单的一行代码,在前一篇文章中已经对委托方法的调用进行了分析,得出此处会转化为对Invoke方法的调用,需要明白的是Invoke方法并非Delegate或是MulticastDelegate中的方法,而是编译器自动生成的。如下IL代码:


.method public hidebysig newslot virtual instance void Invoke() runtime managed
{
}


我们看出Invoke方法是一个空方法,没有任何的实现代码,那么这个空方法却又如何能对委托的方法进行调用通过Reflector得到Invoke的C#代码如下:

[MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]
public virtual void
 Invoke();

我们看到,Invoke方法上贴了一个MethodImpl特性(即Attribute),通过MethodImpl特性标记Invoke方法的实现由CLR运行时提供(MethodCodeType.Runtime),至于CLR为Invoke方法提供的实现代码,以本人的水平是无从探究了,不过其中用到反射技术应该是毋庸置疑的。其实查看一下Delegate类中的方法,发现有DynamicInvokeDynamicInvokeImpl方法其中在DynamicInvoke中对DynamicInvokeImpl进行了调用,而在DynamicInvokeImpl方法中则是运用.NET的反射技术对委托方法进行了调用,所以我们可以猜想一下,CLR运时应该是把对Invoke方法的调用桥接为对DynamicInvoke的调用

   OK!讨论到这里就要结束了,本文是把自己对委托的理解记录于此,希望对委托不甚了解的朋友对.NET中的这项技术有一个比较好的认识,但本文探讨的毕竟是理论,而委托在实践中应用也颇为广泛,所以运用委托解决实际问题能力,还是要靠各自日常的修炼了。

另外附上本文VB.NET代码实例,方便学习VB.NET的朋友们对照: