CLR笔记:6.类型和成员基础

1.Class的可见性有public和internal两种,public对所有程序集都可见,internal仅对其所在的程序集可见。默认是internal的。

2.友元程序集,
    使用friend assembly可以实现单元测试,而不使用反射技术。
    书上讲的是按照命令行编译。

    我测试用的是vs2005的solution,如下:


3.成员的可访问性
    成员默认是private的,接口类型的成员都是public的。
    子类重写父类的成员时,原始成员与重写成员要有相同的可访问性——C#的约束;CLR的约束是,重写成员的可访问性不能更低。
    
    CLR和C#是不一样的,如表:

CLR术语 C#术语
Private private
Family protected
Family and Assembly 不支持
Assembly internal
Family or Assembly protected internal
Public public

4.静态类
static只能用于class,不能用于struct,因为CLR要求值必须实例化,而且不能控制实例化过程。
C#对静态类的约束:
    静态类必须直接从System.Object派生
    静态类不能实现任何接口
    静态类只能定义静态成员:字段,方法,属性,事件
    静态类不能用作:字段,方法,参数,局部变量。
在MSIL中,不会为静态类生成ctor,会将其标记为abstract和sealed

5.部分类
    CLR不支持partial,只是C#的语法。所以某个类型的源码必须使用同一种编程语言

6.组件,多态和版本控制
.NET版本号2.7.1.34,包含4个部分:主版本号,次版本号,内部版本号,修订版本号。
    修订版本,向后兼容,改变内部/修订版本号;
    发布新版本,不向后兼容,改变主/次版本号。
多态中,子类重写父类的虚方法,会引起版本控制问题,即父类发生改变,其版本低于子类版本,会导致子类行为变化。
C# 5个用于 类/类成员 的 影响组件版本控制 的 关键字:
    abstract:用于类/类成员
    virtual和override:用于成员
    sealed:用于类/类成员。用于成员时,仅用于重写了虚方法的方法。
    new,用于类/类成员/常量/字段

C#调用虚方法:
    CLR允许类中定义多个"同名方法",仅仅是返回类型不同,IL允许这样做;C#不允许,忽略返回值的类型,相应的用"转换操作符"实现IL中的"同名方法"。
    
    调用方法相应的MSIL:
        一个是call,用来调用静态方法,实例方法和虚方法。必须要指定调用方法的类型(对于静态方法)或者对象(对于实例方法/虚方法),如果在该类型/对象中找不到该方法,会检查其基类来匹配方法。
        另一个是callvirt,用来调用实例方法和虚方法,不能用于调用静态方法。必须要指定调用方法的实例对象,如果这个对象为null,会抛出NullReferenceException异常,这意味着每次调用前都会有额外的null检查,从而比调用call慢一些。
        如下代码所示:
    public sealed class Program
    
{
        
public Int32 GetFive()
        
{
            
return 5;
        }


        
public static void Main()
        
{
            Program p 
= null;
            Int32 x 
= p.GetFive();  //在C#中,使用callvirt,会抛出NullReferenceException异常
        }

    }
     
        在C#编译器中,使用callvirt调用所有实例方法(包括虚方法),使用call调用所有静态方法。对于其他的编译器,这一点不能保证,所以在虚方法和非虚方法之间改动而不重新编译,会产生无法预测的问题。
        C#使用call而不用callvirt调用虚方法的特例:ToString,见下:
    internal class SomeClass
    
{
        
public override string ToString()
        
{
            
return base.ToString();
        }

    }
        这时候,生成call的IL代码。因为如果使用callvirt,意味着这时一个虚方法,从而递归执行该方法,直到AppDomain的堆栈溢出。

        在调用值类型定义的方法时,使用call。这是因为,首先,值类型是密封的,从而不存在虚方法;另外,值类型永远不会为null,所以永远不会抛出NullReferenceException异常;再者,如果使用callvirt,就要使用装箱机制,性能会有极大影响。
        
        在设计class的过程中,要尽量少定义虚方法。取代办法:可以定义一组重载方法,经其中最复杂的方法虚拟化,而将所有有用的重载非虚拟化,示例如下:
    public class Set
    
{
        
private Int32 m_length = 0;

        
//这个有用的重载是非虚拟的
        public Int32 Find(Object value)
        
{
            
return Find(value, 0, m_length);
        }


        
//这个有用的重载是非虚拟的
        public Int32 Find(Object value, Int32 startIndex)
        
{
            
return Find(value, 0, m_length - startIndex);
        }


        
//功能最丰富的方法是虚拟的,可以被重写
        public Int32 Find(Object value, Int32 startIndex, Int32 endIndex)
        
{
            .
//具体实现
        }

    }

    sealed密闭类尽量使用。将sealed改为非密闭的容易,反之困难;性能也快,因为sealed一定是非虚拟的,从而编译器不用考虑其派生类,而虚方法的性能不如非虚方法;因为密闭了,所以安全性和可预测性也就高。

    子类中有父类的方法,会警告,不会报错。这时要使用new关键字,告诉CLR,新旧方法没有任何关系。
原文地址:https://www.cnblogs.com/Jax/p/890812.html