6.類型和成員基礎
1.Class的可見性有public和internal兩種,public對所有程序集都可見,internal僅對其所在的程序集可見。默認是public的。
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一定是非虛擬的,從而編譯器不用考