CLR via C#-类型和类型成员

类型的各种成员

类型的成员有:常量、字段、实例构造器、类型构造器、方法、操作符重载、转换操作符、属性、事件、类型

以下C#代码展示了一个类型定义,其中包含所有可能的成员。

public sealed class SomeType{
    //嵌套类
    private class SomeNestedType { }

    //常量、只读和静态可读/可写字段
    private const int constantField = 1;
    private readonly String readOnlyField = "2";
    private static int staticField = 3;

    //类型构造器
    static SomeType() { }

    //实例构造器
    public SomeType() { }
    public SomeType(int x) { }

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

    //实例属性
    public int SomeProp{ get; set; }

    //实例有参属性(索引器)
    public int this[String s]{ get; set;}

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

类型的可见性

定义文件范围的类型时,可将类型的可见性指定为public或internal。

public类型不仅对定义程序集中的所有代码可见,还对其他程序集中的代码可见。

internal类型则仅对定义程序集中的所有代码可见,对其他程序集中的代码不可见。

定义类型时不显式指定可见性,C#编译器会帮你指定为internal。

//public即可有本程序集也可由其他程序集的代码访问
public class ThisIsAPublicType{}
//internal,只可有本程序集的代码访问
internal class ThisisAnInternalType{}
//没有显式声明,默认为internal
class ThisIsAlsoAnInternalType{}

成员的可访问性

成员的可访问性按照限制最大到限制最小排列:

private,成员只能由定义类型或任何嵌套类型中的方法访问

protected,成员只能由定义类型,任何嵌套类型或者不管在什么程序集中的派生类型中的方法访问

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

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

public,成员可由任何程序集的任何方法访问

成员默认是private

如果没有显式声明成员的可访问性,编译器通常默认选择private。

接口成员默认是public

CLR要求接口类型的所有成员都具有public可访问性,因此编译器自动将所有接口成员的可访问性设为public。

原始成员与重写成员必须具有相同的可访问性

派生类型重写基类型定义的成员时,C#编译器要求原始成员与重写成员具有相同的可访问性。


合理使用类型的可见性和成员的可访问性

默认为密封类的优势

定义新类型时,编译器应默认生成密封类,使他不能作为基类使用。但是包括C#编译器在内的许多编译器都默认生成非密封类。

密封类之所以比非密封类好,有以下三个方面的原因

①版本控制

②性能

③安全性和可预测性

定义类时遵循的原则

显式指定类为sealed

定义类时,除非确定要将其作为基类,并允许派生类对他进行转化,否则总是显式地指定为sealed。类默认为internal。

字段定义为private

类的内部,将数据字段定义为private。

避免将成员定义为protected或internal

在类的内部,将自己的方法属性事件定义为private和非虚。当然也会将某个定义为public,一边公开类型的某些功能。

尽量避免上述任何成员定义为protected或internal,因为这使类型面临更大的安全风险。迫不得已,也会尽量选择protected或internal。

virtual永远最后才考虑,因为虚成员会放弃许多控制,丧失独立性,变得彻底依赖于派生类的正确行为。

定义辅助类封装独立的功能

当算法的实现开始变复杂时,定义一些辅助类型来封装独立的功能。

如果定义的辅助类型只有一个超类型使用,就在超类型中嵌套这些辅助类型。

这样除了可以限制范围,还允许嵌套的辅助类型中的代码引用超类型中定义的私有成员。

VS的代码分析工具强制执行了一条设计规则,即对外公开的嵌套类型必须在文件或程序集范围中定义,不能在另一个类型中定义。

因为一些开发人员觉得引用嵌套类型时,所用的语法过于繁琐。


静态类

有一些永远不需要实例化的类,例如Console,Math等。这些类只有static成员。

事实上,这种类的唯一作用就是组合一组相关的成员。例如Math类就定义了一组执行数学运算的方法。

在C#中,要用static关键字定义不可实例化的类。该关键字只能用于类,不能用于结构(值类型)。因为CLR总是允许值类型实例化,这是没办法阻止的。

静态类的限制

①静态类直接从基类System.Object派生,从其他任何基类派生都没有意义。

静态类不能实现任何接口,只有使用类的实例时,才可调用类的接口方法。

静态类只能定义静态成员(字段,方法,属性,事件),任何实例成员都会导致编译器报错。

静态类不能作为字段、方法参数或局部变量使用,因为他们都代表引用了实例的变量,不允许,会报错。

下面是一个定义了静态成员的静态类。代码虽然能通过编译,有一个警告,但该类没有做任何有意义的事情。

public static class AStaticClass
{
    public static void AStaticMethod(){}
    public static String AStaticProperty(){
        get{return s_AStaticField;}
        set{s_AStaticField=value;}
    }
    private static String s_AStaticField;
    public static event EventHandler AStaticEvent;
}

使用关键字static定义类,将导致C#编译器将该类标记为abstract和sealed。编译器不在类型中生成实例构造器方法。


分部类

partial关键字告诉C#编辑器:类、结构或接口的定义源代码可能要分散到一个或多个源代码文件中。

将类型源代码分散到多个文件的原因有三。

源代码控制

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

在同一个文件中将类或结构分解成不同的逻辑单元

创建一个类型提供多个功能,使类型能提供完整解决方案。为

了简化实现,有时会在一个源代码文件中重复声明同一个分部类型。

然后分部类型的每个部分都实现一个功能,并配以他的全部字段、方法、属性和事件等。

这样就可以方便地看到组合以提供一个功能的全体成员,从而简化编码。

代码拆分


常量

基元类型与非基元类型常量

基元类型常量

常量是值从来不变化的符号。定义常量符号时,他的值必须能在编译时确定。

确定后,编译器将常量值保存在程序集元数据中。这意味着只能定义编译器识别的基元类型的常量。

非基元类型常量

C#也允许定义非基元类型的常量变量constant variable,前提是把值设为null。

public sealed class SomeType{
    //SomeType不是基元类型但C#允许置为null的这种;类型的常量变量
    public const SomeType Empty=null;
}

常量的值直接嵌入代码

常量值从不变化,所以常量总是被视为静态成员,而不是实例成员。

定义常量将会创建元数据。代码引用常量符号时,编译器在定义常量的程序集的元数据中查找该符号,提取常量的值,将值嵌入生成的IL代码中。

所以在运行时不需要为常量分配任何内存。除此之外,不能获取常量的地址,也不能以传引用的方式传递常量。

这些限制意味着常量不能很好的支持跨程序集的版本控制。因此只有确定一个符号的值从不变化才定义常量。

如果希望运行时从一个程序集中提取另一个程序集中的值,那不应该使用常量,而应该使用readonly字段。

 


字段

字段是一种数据成员,其中容纳了一个值类型的实例或者对一个引用类型的引用。

字段的内存分配

CLR支持类型字段和实例字段。如果是类型字段,容纳字段数据所需的动态内存是在类型对象中分配的。

通常是在引用了该类型的任何方法首次进行JIT编译的时候,将类型加载到一个AppDomain时,创建类型对象。

如果是实例字段,容纳字段数据所需的动态内存是在构造类型的实例时分配。

由于字段存储在动态内存中,所以他们的值在运行时才能获取。

字段解决了常量存在的版本控制问题。而且字段可以是任何数据类型,不像常量仅仅局限于编译器内置的基元类型。

只读字段和可读写字段

CLR支持只读字段和可读写字段。

可读写意味着在代码执行过程中,字段值可多次改变。只读字段只能在构造器方法中写入。

编译器和验证机制确保只读字段不会被构造器以外的任何方法写入。不过可利用反射来修改只读字段。

以常量的代码为例,可以使用一个静态只读字段代替常量来修正版本控制问题。

public sealed class SomeLibraryType{
    public static readonly Int32 MaxEntriesInList=50;
}

假设DLL程序集到开发人员将50修改为100,并重新生成程序集。

当应用程序代码重新执行时,她将自动提取字段到新值100.应用程序不需要重新生成就可以直接运行。

引用类型只读字段

当某个字段是引用类型,并且该字段被标记为readonly时,不可改变的是引用,而非字段引用的对象。

public sealed class AType{
    public static readonly Char[] InvalidChars=new Char[]{'A','B','C'};
}
public sealed class AnotherType{
    public static void M(){
        //下面三行代码是合法的,可以通过编译
        AType.InvalidChars[0]='X';
        AType.InvalidChars[1]='Y';
        AType.InvalidChars[2]='Z';
        //下面一行代码是非法的,无法通过编译
        //因为不能让InvalidChars引用别的东西
        AType.InvalidChars=new Char[]{'X','Y','Z'};
    }
}
原文地址:https://www.cnblogs.com/errornull/p/9748961.html