架构深渊

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

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

Posted on 2009-01-02 01:56  chen eric  阅读(259)  评论(0编辑  收藏  举报
局部类型
  C# 
1.1中要求将类的全部代码放在一个文件中。而在C# 2.0允许将类或结构的定义和实现分开放在多个文件中。通过使用新的partial关键字来标注分割,可以将类的一部分放在一个文件中,而将另一个部分放在一个不同的文件中。例如,可以将下面的代码放到文件MyClass1.cs中:

public partial class MyClass
{
    
public void Method1()
    {}
}

  在文件MyClass2.cs中,可以插入下面的代码:

public partial class MyClass
{
    
public void Method2()
    {}

    
public int Number;
}

  实际上,可以将任一特定的类分割成任意多的部分。局部类型支持可以用于类、结构和接口,但是不能包含局部枚举定义。局部类型是一个非常有用的功能。有时,需要修改机器生成的文件,例如Web服务客户端包装类。然而,当重新生成此包装类时,对该文件的修改将会被丢弃。通过使用局部类,可以将这些改变分开放在单独的文件中。在ASP.NET中可以将局部类用于code
-beside编辑(从code-behind演变而来),单独存储页面中机器生成的部分,而在Windows窗体中使用局部类来存储InitializeComponent方法的可视化设计器输出以及成员控件。通过使用局部类型,两个或者更多的开发人员可以工作在同一个类型上,同时都可以从源代码控制中签出其文件而不互相影响。

  但是,如果多个不同的部分对同一个类做出了相互矛盾的定义会出现什么样的后果?答案很简单。一个类(或一个结构)可能具有两个不同的方面或性质:累积性的(accumulative)和非累积性的(non
-accumulative)。累积性的方面是指类可以选择添加它的各个部分,比如接口派生、属性、索引器、方法和成员变量。例如,下面的代码显示了一个部分是如何添加接口派生和实现的:

public partial class MyClass
{}

public partial class MyClass : IMyInterface
{
    
public void Method1()
    {}

    
public void Method2()
    {}
}

  非累积性的方面是指一个类型的所有部分都必须一致。无论这个类型是一个类还是一个结构,类型可见性(公共或内部)和基类都是非累积性的方面。例如,下面的代码不能编译,因为并非MyClass的所有部分都出现在基类中:

public class MyBase
{}

public class SomeOtherClass
{}

public partial class MyClass : MyBase
{}

public partial class MyClass : MyBase
{}

//Does not compile
public partial class MyClass : SomeOtherClass
{}

  除了所有的部分都必须定义相同的非累积性部分以外,只有一个部分能够重写虚方法或抽象方法,并且只有一个部分能够实现接口成员。

  C# 
2.0是这样来支持局部类型的:当编译器构建程序集时,它将来自多个文件的同一类型的各个部分组合起来,并用中间语言(Microsoft intermediate language, MSIL)将这些部分编译成单一类型。生成的中间语言中不含有哪一部分来自哪个文件的记录。正如在C# 1.1中一样。另外值得注意的是,局部类型不能跨越程序集,并且通过忽略其定义中的partial限定符,一个类型可以拒绝包含其他部分。

  因为编译器所做的只是将各个部分累积,所以一个单独的文件可以包含多个部分,甚至是包含同一类型的多个部分,尽管这样做的意义值得怀疑。

  在C#中,开发人员通常根据文件所包含的类来为文件命名,这样可以避免将多个类放在同一个文件中。在使用局部类型时,建议在文件名中指示此文件包含哪个类型的哪些部分(例如MyClassP1.cs、MyClassP2.cs),或者采用其他一致的方式从类的名称上指示源文件的内容。例如,Windows窗体设计人员将用于该窗体的局部类的一部分存放在Form1.cs中,并将此文件命名为Form1.Designer.cs。

  局部类的另一个不利之处是,当开始接触一个不熟悉的代码时,所维护的类的各个部分可能遍布在整个项目的文件中。在这种情况下,可以使用Visual Studio Class View,因为它可以将一个类型的所有部分积累起来展示给您,并允许通过单击它的成员来导航各个不同的部分。导航栏也提供了这个功能。

 匿名方法
  C#支持用于调用一个或多个方法的委托(
delegate)。委托提供运算符和方法来添加或删除目标方法,它也可以在整个.NET框架中广泛地用于事件、回调、异步调用、多线程等。然而,仅仅为了使用一个委托,有时不得不创建一个类或方法。在这种情况下,不需要多个目标,并且调用的代码通常相对较短而且简单。在C# 2.0中,匿名方法是一个新功能,它允许定义一个由委托调用的匿名(也就是没有名称的)方法。例如,下面是一个常规SomeMethod方法的定义和委托调用:

class SomeClass
{
    
delegate void SomeDelegate();

    
public void InvokeMethod()
    {
        SomeDelegate del 
= new SomeDelegate(SomeMethod); 
        del(); 
    }

    
void SomeMethod() 
    { 
        MessageBox.Show(
"Hello");
    }
}

    可以用一个匿名方法来定义和实现这个方法:

class SomeClass
{
    
delegate void SomeDelegate();

    
public void InvokeMethod()
    {
        SomeDelegate del 
= delegate() 
        {
            MessageBox.Show(
"Hello");
        };
        del(); 
    }
}

  匿名方法被定义为内联方法,而不是作为任何类的成员方法。此外,无法将方法属性应用到一个匿名方法,并且匿名方法也不能定义泛型类型或添加泛型约束。

  关于匿名方法有两个地方值得注意:delegate保留关键字的重载使用和委托指派。稍后,将看到编译器如何实现一个匿名方法,而通过查看代码,就可以了解编译器如何推理所使用的委托的类型,实例化推理类型的新委托对象,将新的委托包装到匿名方法中,并将其指派给匿名方法定义中使用的委托(前面的示例中的del)。

  匿名方法可以用在任何需要使用委托类型的地方。可以将匿名方法传递给任何方法,只要该方法接受适当的委托类型作为参数即可:

class SomeClass
{
    
delegate void SomeDelegate();

    
public void SomeMethod() 
    { 
        InvokeDelegate(
delegate(){MessageBox.Show("Hello");}); 
    } 

    
void InvokeDelegate(SomeDelegate del) 
    { 
        del(); 
    }
}

  如果需要将一个匿名方法传递给一个接受抽象Delegate参数的方法,例如:

void InvokeDelegate(Delegate del);

  则首先需要将匿名方法强制转换为特定的委托类型。

  下面是一个将匿名方法作为参数传递的具体的实用例子,它在没有显式定义ThreadStart委托或线程方法的情况下启动一个新的线程:

public class MyClass

    
public void LauchThread()
    {
        Thread workerThread 
= new Thread(delegate()
        {
            MessageBox.Show(
"Hello");
        }); 

        workerThread.Start();
    }
}

    在前面的示例中,匿名方法被当作线程方法来使用,这会导致消息框从新线程中显示出来。将参数传递到匿名方法。

    当定义带有参数的匿名方法时,应该在delegate关键字后面定义参数类型和名称,就好像它是一个常规方法一样。方法调用必须与它指派的委托的定义相匹配。当调用委托时,可以传递参数的值,与正常的委托调用完全一样:

class SomeClass
{
    
delegate void SomeDelegate(string str);

    
public void InvokeMethod()
    {
        SomeDelegate del 
= delegate(string str)
        {
            MessageBox.Show(str);
        };
        del(
"Hello"); 
    }
}

    如果匿名方法没有参数,则可以在delegate关键字后面使用一对空括号:

class SomeClass
{
    
delegate void SomeDelegate();

    
public void InvokeMethod()
    {
        SomeDelegate del 
= delegate()
        {
            MessageBox.Show(
"Hello");
        };
        del(); 
    }
}

    然而,如果将delegate关键字与后面的空括号一起忽略,则定义一种特殊的匿名方法,它可以指派给具有任何调用的任何委托:

class SomeClass
{
    
delegate void SomeDelegate(string str);

    
public void InvokeMethod()
    {
        SomeDelegate del 
= delegate 
        {
            MessageBox.Show(
"Hello");
        };
        del(
"Parameter is ignored"); 
    }
}

    很明显,如果匿名方法并不依赖于任何参数,而且想要使用这种与委托调用无关的方法代码,则只能使用这样的语法。

    注意,当调用委托时,仍然需要提供参数,因为编译器为从委托签名中推理的匿名方法生成无名参数,就好像编写了下面的代码(C#伪码)一样:

SomeDelegate del 
= delegate(string
{
    MessageBox.Show(
"Hello");
};

    此外,不带参数的匿名方法不能与带参数的委托一起使用。

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

    
public void InvokeMethod()
    {
        
string msg = "Hello";
        SomeDelegate del 
= delegate(string name)
        {
            MessageBox.Show(msg 
+ m_Space + name);
        };
        del(
"Juval"); 
    }
}

图 
7 匿名方法代码中的局部变量

    匿名方法可以使用任何类成员变量,并且它还可以使用定义在其包含方法范围之内的任何局部变量。图7对此进行了展示。一旦知道如何为一个匿名方法传递参数,也就可以很容易地定义匿名事件处理,如图8所示。

public class MyForm : Form

    Button m_MyButton; 

    
public MyForm() 
    { 
        InitializeComponent(); 

        m_MyButton.Click 
+= delegate(object sender,EventArgs args) 
        { 
            MessageBox.Show(
"Clicked"); 
        }; 
    } 

    
void InitializeComponent() 
    {}
}

图 
8 匿名方法作为事件处理程序

    因为
+=运算符仅仅将一个委托的内部调用列表与另一个委托的内部调用列表连接起来,所以可以使用+=来添加一个匿名方法。注意,在匿名事件处理的情况下,不能使用-=运算符来删除事件处理方法,除非将匿名方法作为处理程序加入,要这样做,可以首先将匿名方法存储为一个委托,然后通过事件注册该委托。这样,可以将-=运算符作用于存储的委托来取消注册。