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 |
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异常
}
}
{
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的堆栈溢出。{
public override string ToString()
{
return base.ToString();
}
}
在调用值类型定义的方法时,使用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)
{
.//具体实现
}
}
{
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,新旧方法没有任何关系。