架构深渊

慢慢走进程序的深渊……关注领域驱动设计、测试驱动开发、设计模式、企业应用架构模式……积累技术细节,以设计架构为宗。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

在Visual C# 2.0中创建优雅代码5

Posted on 2009-01-02 01:57  chen eric  阅读(196)  评论(0编辑  收藏  举报
匿名方法的实现
    编译器为匿名方法生成的代码很大程度上依赖于匿名方法使用的参数或变量的类型。例如,匿名方法使用其包含方法的局部变量(也叫做外层变量)还是使用类成员变量和方法参数。无论是哪一种情况,编译器都会生成不同类型的中间代码。如果匿名方法不使用外层变量(也就是说,它只使用自己的参数或者类成员),则编译器会将一个私有方法添加到该类中,以便赋予方法一个唯一的名称。该方法的名称具有以下格式:

__AnonymousMethod$()

    和其他编译器生成的成员一样,这都是会改变的,并且最有可能在最终版本发布之前改变。方法调用将成为它指派的委托的调用。

    编译器只是简单地将匿名方法定义和赋值转换成所推理的委托类型的标准实例,以包装机器生成的私有方法:

SomeDelegate del 
= new SomeDelegate(__AnonymousMethod$00000000);

    非常有趣的是,机器产生的私有方法并不显示在智能感知(IntelliSense)中,也不能显式地调用它,因为其名称中的美元符号对于C#方法来说是一个非法标记(但它是一个有效的中间代码标记)。当匿名方法使用外部变量时,情况会更加困难。如果这样,编译器将用下面的格式添加具有唯一名称的私有嵌套类:

__LocalsDisplayClass$

    嵌套类有一个指向包含类的this引用,它是一个有效的中间代码成员变量名。嵌套类包含与匿名方法使用的每个外层变量对应的公共成员变量。编译器向嵌套类定义中添加一个具有唯一名称的公共方法,格式如下:

__AnonymousMethod$()

    方法调用将成为被指派的委托的调用。编译器用新的代码来替代匿名方法定义,此代码创建一个嵌套类的实例,并进行必要的从外层变量到该实例的成员变量的赋值。最后,编译器创建一个新的委托对象,以便包装嵌套类实例的公共方法,然后调用该委托来调用此方法。图9用C#伪代码展示了编译器为图7中定义的匿名方法生成的代码。

class SomeClass
{
    
string m_Space = " "
    
delegate void SomeDelegate(string str);

    
private sealed class __LocalsDisplayClass$00000001
    {
        
public SomeClass <this>//Back pointer, name is valid in MSIL
        public string msg; //Outer variable

        
public void __AnonymousMethod$00000000(string name) 
        {
            MessageBox.Show(msg 
+ <this>.m_Space + name);
        }
    }

    
public void InvokeMethod() 
    {
        
string msg = "Hello";
        __LocalsDisplayClass$
00000001 locals;
        locals 
= new __LocalsDisplayClass$00000001();
        locals.
<this> = this;
        locals.msg 
= msg;
        SomeDelegate del 
= new 
        SomeDelegate(locals.__AnonymousMethod$
00000000);
        del(
"Juval");
    }
}

图 
9 使用外部变量的匿名方法代码

 泛型匿名方法
    一个匿名方法可以使用泛型参数类型,就像其他方法一样。它可以使用在类范围内定义的泛型类型,例如:

class SomeClass

    
delegate void SomeDelegate(T t); 

    
public void InvokeMethod(T t) 
    { 
        SomeDelegate del 
= delegate(T item){
        del(t); 
    }
}

    因为委托可以定义泛型参数,所以匿名方法可以使用在委托层定义的泛型类型。可以指定用于方法调用的类型,在这种情况下,方法调用必须与其所指派的委托的特定类型相匹配:

class SomeClass

    
delegate void SomeDelegate(T t); 

    
public void InvokeMethod() 
    { 
        SomeDelegate del 
= delegate(int number) 
        { 
            MessageBox.Show(number.ToString()); 
        }; 
        del(
3); 
    }
}

    在匿名方法例子中,匿名方法虽然看起来有点像另类的编程技术,但是它是相当有用的,因为在只要一个委托就足够的情况下,使用它就可以不必再创建一个简单方法。图10展示了一个有用的匿名方法的实际例子—SafeLabel Windows窗体控件。

    Windows窗体依赖于基本的Win32消息。因此,它继承了典型的Windows编程要求:只有创建窗口的线程可以处理它的消息。在.NET框架2.0中,调用错误的线程总会触发一个Windows窗体方面的异常。因此,当在另一个线程中调用窗体或控件时,必须将该调用发送到正确的所属线程中。Windows窗体有内置的支持,可以解决这个问题,其方法是用Control基类实现ISynchronizeInvoke接口,其定义如下:

public interface ISynchronizeInvoke 
{
    
bool InvokeRequired {get;}
    IAsyncResult BeginInvoke(Delegate method,
object[] args);
    
object EndInvoke(IAsyncResult result);
    
object Invoke(Delegate method,object[] args);
}

    Invoke方法接受针对所属线程中的方法的委托,并且将调用从正在调用的线程发送到该线程。因为方法可能并不总是知道自己是否真的在正确的线程中执行,所以通过使用InvokeRequired属性,可以进行查询,从而弄清楚是否需要调用Invoke方法。但是问题是使用ISynchronizeInvoke接口将会大大增加编程模型的复杂性,因此较好的方法往往是将带有ISynchronizeInvoke接口的交互封装在控件或窗体中,它们会自动地按照需要使用ISynchronizeInvoke。

public class SafeLabel : Label
{
    
delegate void SetString(string text);
    
delegate string GetString();

    
override public string Text
    {
        
set
        {
            
if(InvokeRequired)
            {
                SetString setTextDel 
= delegate(string text)
                    {
base.Text = text;};
                Invoke(setTextDel,
new object[]{value});
            }
            
else
                
base.Text = value;
        }
        
get
        {
            
if(InvokeRequired)
            {
                GetString getTextDel 
= delegate(){return base.Text;};
                
return (string)Invoke(getTextDel,null);
            }
            
else
                
return base.Text;
        }
    }
}

图 
10 SafeLabel 控件

    例如,为了替代公开Text属性的Label控件,可以定义从Label派生的SafeLabel控件,如图10所示。SafeLabel重写了其基类的Text属性。在其get和set中,检查Invoke是否是必须的。如果是这样,则它需要使用一个委托来访问此属性。该实现实现了在正确的线程上调用了基类属性的实现。因为SafeLabel只定义这些方法,所以它们可以通过委托进行调用。这些方法都是匿名方法很好的候选者。SafeLabel传递这样的委托,以便将匿名方法作为其Text属性的安全实现包装到Invoke方法中。