类型中可包含的成员 :

  • 常量 :数据值恒定不变的符号。(逻辑实质是静态static成员,因此与类型关联,不与实例关联)

  • 字段 :read/write的数据值。

  1. 静态(static)字段 :表示该字段为类型状态的一部分。
  2. 非静态的字段 :表示该字段与实例对象状态的一部分。
  • 构造器 :将字段初始化为良好初始状态的特殊方法。
  1. 实例构造器 :非static构造器,作用于非静态字段(实例对象字段)。
  2. 类型构造器 :static构造器,作用于静态字段(类型状态字段)。
  • 方法 :更改或查询状态的函数。(通常要读写类型或是对象的字段)
  1. 静态(static)方法 :作用于类型。
  2. 实例(非静态)方法 : 作用于所属类型的实例对象。
  • 操作符重载 :实质是方法,其中定义了当操作符作用于该类型的对象时,应该如何操作该对象。(不是所有编程语言都支持该函数方法,所以非CLS一部分)

  • 转换操作符 :实质是方法,其中定义了如何隐式或显式将类型的对象从一种类型转型为另一种类型的方法。(不是所有编程语言都支持该函数方法,所以非CLS一部分)

  • 属性 :属性允许用简单的、字段风格的语法设置(set)或查询(get)类型或对象的逻辑状态,同时保证状态不被破坏。(C++中没有,特别注意)

  1. 静态属性(static)。
  2. 实例属性(非static)。
    (属性基本无参数,有参数及其少见,集合类用的相对较多)
  • 事件 :引发事件通常是为了响应提供事件的类型或对象的状态改变,进而向订阅该事件的对象发送通知。其中包含了两个方法,允许静态或实例方法登记或注销对该事件的订阅。除了此之外,通常事件通常还用一个委托字段来维护已登记的方法集。
  1. 静态事件 :允许类型向一个或多个静态或实例方法发送通知。
  2. 实例事件(非static) :允许实例对象向一个或多个静态或实例方法发送通知。
  • 类型 :实质是嵌套类型,通常用这个方法将大的、复杂的类型分解成更小的构建单元(building block)来简化实现。

编译器会为上述每种成员都生成 元数据 IL代码 所有编程语言生成的元数据格式完全一致,这样 CLR 才能成为 公共语言运行时 。)
元数据是所有语言都生成和使用的公共信息,正因为有它,用一种语言写的代码才能无缝访问用另一种语言写的代码, Microsoft.NET Framework 开发平台的关键,实现了编程语言类型和对象的无缝集成。

类型成员定义代码演示 :

using System;

public sealed class SomeType
{
    //嵌套类
    private class SomeNestedType{}
    //常量、只读和静态可读/可写字段
    private const Int32 c_SomeConstant = 1;
    private readonly String m_SomeReadOnlyField = "2";
    private static Int32 s_SomeReadWriteField = 3;

    //类型构造器
    public SomeType(Int32 x){}
    public SomeType(){}

    //实例方法和静态方法
    private String InstanceMethod()
    { return null;}
    public static void Main(){}

    //实例属性
    public Int32 SomeProp
    {
      get{ return 0;}
      set{}
    }

    //实例有参属性(索引器)
    public Int32 this[String a]
    {
      get{ return 0;}
      set{}
    }

    //实例事件
    public event EventHandler SomeEvent;
}

类型的可见性

友源程序集 :

使用System.Runtime.CompilerServices命名空间中的InternalsVisibleTo特性来标明友源程序集
using System;
using System.Runtime.CompilerServices;

//当前程序集中的internal类型可由以下两个程序集中的任何代码访问(不管什么版本或语言文化)
[assembly:InternalsVisibleTo("Wintellect, PublicKey = 12345678...90abcdef")]
[assembly:InternalsVisibleTo("Microsoft, PublicKey = b77a5c56...1934e089")]
internal sealed class SomeInternalType {...}
internal sealed class AnotherInternalType {...}
以下代码展示Wintellect友元程序集如何访问上述程序集中的internal类型SomeInternalType:
using System;

internal sealed class Foo
{
    private static Object SomeMethod()
    {
      //这个"这个Winteleect"程序集能访问另一个程序集的internal类型,就好像那是public访问限制的。
      SomeInternalType sit = new SomeInternalType();
      return sit;
    }
}

成员的可访问性

  • private :成员只能由定义类型或任何嵌套类型中的方法访问。(类中成员不加访问限制,默认都是private,如非必要都将字段设为private来确保安全性)

  • protected :成员只能由定义类型、任何嵌套类型或者不管在什么程序集中的派生类型中的方法访问。(如非必要不要设置,可能会造成类之间的过度耦合和预期之外的结果。)

  • internal :成员只能由定义程序集中的方法访问。

  • protected internal :成员可由任何嵌套类型任何派生类型不管在什么程序集中)或者定义程序集中的任何方法访问。

  • public :成员可由任何程序集中的任何方法访问。(当需要公开类型中的某些方法和字段时使用,尽量不要使用,否则会造成类型不安全,会被外部操作破坏。)

从基类派生时,只能放宽访问的限制:如private ——>public,而不能相反收紧限制,因为派生类型必须要能够转化为基类型使用多态性))

静态类

静态类是永远不需要实例化的类。
静态类的限制:
  • 静态类只能由基类派生。(这意味着静态类不适用于继承体系,因为继承只适用于对象,而不能创建静态类的实例。)

  • 静态类不能实现任何接口。(因为只有使用类的实例时,才可调用类的接口方法,而静态类不能创建实例。)

  • 静态类只能定义静态成员。 (调用静态构造器对该静态类初始化。)

  • 静态类不能作为字段、方法参数或局部变量使用。(因为它们都代表引用了实例的变量。)

分布类:

  • partial关键字定义,可将类型的代码分散到多个源代码文件中,每个文件都可单独签出,多个程序员能同时编辑类型。

组件、多态和版本控制

组件软件编程(CSP)的特点:
  • 组件(.NET Framework中称为程序集)有已经发布的意思。

  • 组件永远维持自己的标识,.NET中使用动态链接

  • 组件清楚指明它所依赖的组件(引用源数据表)

  • 组件应编档它的类和成员

  • 组件必须制定它需要的安全权限

  • 组件要发布在任何“维护版本”中都不会改变的接口

组件关键字对组件版本控制的影响:
  • abstract
  1. 用于类型 :表示不能构造该类型的实例(抽象类),同时在该抽象类中,所有方法也都必须声明为abstract抽象方法,属性字段可以不为abstract抽象的。
  2. 用于方法 :表示为了构造派生类型的实例,派生类型必须重写并实现这个成员。(成员方法可以只有声明无主体。)
  • virtual虚函数声明,不可用于类型,只能用于方法成员,表示该成员可由派生类型重写(不强制重写区别于abstract,成员方法不能只声明,要有主体实现。)
    虚函数调用性能很低,同时因为它会放弃很多控制,丧失独立性,应该是不得已的选择。

  • override :只能用于方法成员,表示派生类型正在重写基类型的成员。

  • sealed密封

  1. 用于类型:表示该类型不能用作基类型。(制造密封类)
  2. 用于成员 :表示这个成员不能被派生类型重写,只能将该关键字应用于重写虚方法的方法。
  • new :应用于嵌套类型、方法、属性、事件、常量或字段时,表示该成员与基类中相似的成员无任何关系。

CLR如何调用虚方法、属性和事件

该部分需要以后开发经验丰富再回顾:
CLR(IL代码)提供两个方法调用指令:
  • call :可调用静态方法、实例方法和虚方法。该指令经常用于以非虚方式调用虚方法。
  • callvirt :可调用实例方法和虚方法。会验证调用的变量是否为null,所以执行速度比call指令稍慢。
该部分提炼经验如下:
  • 因为调用虚方法性能过低,设计类型时应尽量减少虚方法数量。
  • 设计基类时,通常需要提供一组重载的简便方法。为了提高性能,应该使最复杂的方法成为虚函数,所有重载简便方法成为非虚的方法。

定义类时统一遵循的原则:

  • 设计类时,应该优先显式地将其设为密封类(sealed)这样做:
  1. 便于版本控制 :因为最初密封,将来可以在不破坏兼容性前提下改为非密封,而最初非密封,将来改为密封类可能出现未知的错误。
  2. 可以使用非虚方式调用虚方法 :因为密封类不会有派生类。
  3. 保护自己的状态不允许外界破坏
  • 数据字段应该优先设为private,尽量少用protected、internal、public。因为状态一旦公开,就很容易产生问题,造成对象的行为无法预测,留下未知或是严重的安全隐患。

  • 类的内部设计中,应尽量将方法、属性和事件定义为private和非虚的。只有在需要公开类型的某些功能时,才会将对应成员设为public,以便公开给外界使用,protected和internal是次级选择,virtual永远是最后才考虑的,虚方法会放弃很多控制,丧失独立性。

  • 如果类型要作为基类型使用,增加或是修改它的成员必须非常小心。

使用new关键字声明:
namespace CompanyA
{ 
  public class Phone
  {
    public void Dial()
    {
      Console.WriteLine("Phone.Dial");
      EstablishConnection();    //在这里进行拨号操作
    }
    protected virtual void EstablishConnection()
    {
      Console.WriteLine("Phone.EstablishConnection");    //在这里执行建立连接的操作
    }
  }
}

namespace CompanyB
{
  public class BetterPhone : CompanyA.Phone
  {
    //保留关键字new,指明该方法与基类型中的Dial方法没有关系。
    public new void Dial()
    {
      Console.WriteLine("BetterPhone.Dial");
      EstablishConnection();
      base.Dial();
    }
    
    //为这个方法添加关键字new,指明该方法与基类型的EstablishConnection方法没有关系。
    protected new virtual void EstablishConnection()
    {
      Console.WriteLine("BetterPhone.EstablishConnection");    //在这里执行建立连接的操作。
    }
  }
}
代码输出结果如下所示:

BetterPhone.Dial
BetterPhone.EstablishConnection
Phone.Dial
Phone.EstablishConnection