类型和成员基础
類型的成員
類型中可以定義多種成員,本篇不作深入講解,后續再逐一介紹。
- 常量:數據值值恒定不變的符號。
- 字段:只讀或可讀/可寫的數據值。
- 實例構造器:將對象的實例字段初始化的特殊方法。
- 類型構造器:將類型的靜態字段初始化的特殊方法。
- 方法:更改或查詢類型或對象狀態的函數。
- 操作符重載:操作符重載實際是方法,定義了當操作符作用于對象時,應該如何操作該對象。
- 轉換操作符:定義如何隱式或顯式將對象從一種類型轉換為另一種類型的方法。
- 屬性:屬性允許用簡單的、字段風格的語法設置或查詢類型或對象的邏輯狀態,同時保證狀態不被破壞。
- 事件:靜態事件允許類型向一個或多個靜態或實例方法發出通知。實例事件允許對象向一個或多個靜態或實例方法發送通知。
- 類型:類型可定義其他嵌套類型。
類型的可見性
類型的可見性可以指定為public或internal。public類型對所有程序集可見,而internal類型僅對定義程序集中的所有代碼可見。定義類型時不顯示指定可見性,C#編譯器會默認指定為internal。
友元程序集
某些情況下,我們希望將可見性為internal的類型開放給另外一個特定的程序集訪問,CLR和C#通過友元程序集(friend assembly)提供這方面的支持。生成程序集時,使用System.Runtime.CompilerServices命名空間下的InternalsVisibleTo特性標注友元程序集。該特性獲取標識友元程序集名稱和公鑰的字符串參數。示例如下:
創建類庫項目FriendA和FriendB,分別將類命名為ClassA(Internal)和ClassB
internal class ClassA{...}在FriendA中添加以下特性標記(不要忘記添加System.Runtime.CompilerServices命名空間)
[assembly:InternalsVisibleTo("TeamB,PublicKey=0024...88c4")]在FriendB項目中引用FriendA程序集,成功訪問聲明為Internal的類ClassA
成員的可訪問性
定義類型的成員時,可以指定成員的可訪問性。CLR自己定義了一組可訪問性修飾符,但每種編程語言在向成員應用可訪問性時,都選擇了自己的一組術語以及相應的語法。下表總結了應用于成員的可訪問性修飾符。
| Private | private | 成員只能由定義類型或嵌套類型中的方法訪問 |
| Family | protected | 成員只能由定義類型、嵌套類型或者任意程序集中的派生類型中的方法訪問 |
| Family and Assembly | 不支持 | 成員只能由定義類型、嵌套類型或同一程序集中定義的派生類型中的方法訪問 |
| Assembly | internal | 成員只能由定義程序集中的方法訪問 |
| Family Or Assembly | protected internal | 成員只能由定義程序集中的方法、嵌套類型或任意程序集中的派生類型中的方法訪問 |
| Public | public | 成員可由任何程序集的任何方法訪問 |
在C#中,如果沒有顯示聲明成員的可訪問性,編譯器通常默認選擇private。(接口除外,接口的可訪問性自動設置為public)
派生類型重寫基類型定義的成員時,C#編譯器要求原始成員和重寫成員具有相同的可訪問性。(但CLR不同,CLR允許放寬但不允許收緊成員的可訪問性限制)
靜態類
類可以聲明為static,以指示它僅包含靜態成員。靜態類不能實例化,static關鍵字只能應用于類,不能應用于結構。靜態類相當于一個sealed abstract類。
C#編譯器對靜態類進行了如下限制:
組件、多態和版本控制
本節討論如何通過CLR和編程語言提供的功能來自動適應程序集可能發生的變化。將一個程序集中定義的類型作為另一個程序集中的類型的基類使用時,如果基類版本低于派生類,就可能造成派生類的行為失常。C#提供了5個能影響程序集版本控制的關鍵字,可將它們應用于類型以及類型成員。
| abstract | 不能構造該類型的實例 | 為了構造派生類的實例,派生類型必須重寫并實現這個成員 | (不允許) |
| virtual | (不允許) | 可由派生類重寫 | (不允許) |
| override | (不允許) | 派生類型重寫基類型的成員 | (不允許) |
| sealed | 不能用作基類型 | 不能被派生類型重寫,只能講該類關鍵字應用于重寫虛方法的方法 | (不允許) |
| new | 應用于嵌套類型、方法、屬性、事件、常量或字段,表示該成員與基類中相似的成員無任何關系 | ||
CLR如何調用虛方法、屬性和事件
編譯器在編譯代碼時,會在程序集方法定義表的記錄項中添加一組標志,指明方法是實例方法、虛方法還是靜態方法。生成調用代碼時,編譯器會檢查方法定義的標志,判斷如何生成IL代碼來正確調用方法。CLR提供兩個方法調用指令:
call
該指令可調用靜態方法、實例方法和虛方法。用call指令調用靜態方法,必須指定方法的定義類型。用call指令調用實例方法或虛方法,必須指定引用了對象的變量。call指令假定該變量不為null。
callvirt
該指令可調用實例方法和虛方法,不能調用靜態方法。用callvirt指令調用實例方法或虛方法,必須指定引用了對象的變量。用callvirt指令調用非虛實例方法,變量的類型指明了方法的定義類型。用callvirt指令調用虛實例方法,CLR調查發出調用的對象的實際類型,然后以多態方式調用方法。為了確定類型,發出調用的變量不能為null。JIT編譯器會生成代碼來驗證變量的值是不是null。如果是,callvirt指令造成CLR拋出NullReferenceException異常。這種額外的檢查造成callvirt指令的執行速度比call指令稍慢。
為什么C#編譯器不直接生成call指令呢?
因為C#團隊認為,JIT編譯器應生成代碼來驗證發出調用的對象不為null。
下面來看一段簡單的代碼
class Program {static void Main(string[] args){Console.WriteLine();Object o = new object();o.GetHashCode();o.GetType();} }編譯上述代碼,查看IL如下:
.method private hidebysig static void Main(string[] args) cil managed {.entrypoint// Code size 28 (0x1c).maxstack 1.locals init ([0] object o)IL_0000: nopIL_0001: call void [mscorlib]System.Console::WriteLine()IL_0006: nopIL_0007: newobj instance void [mscorlib]System.Object::.ctor()IL_000c: stloc.0IL_000d: ldloc.0IL_000e: callvirt instance int32 [mscorlib]System.Object::GetHashCode()IL_0013: popIL_0014: ldloc.0IL_0015: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()IL_001a: popIL_001b: ret } // end of method Program::Main我們清楚的可以看見,C#編譯器用call指令調用Console的WriteLine方法,接著用callvirt調用GetHashCode和GetType方法。
轉載于:https://www.cnblogs.com/Answer-Geng/p/7440874.html
總結
- 上一篇: 二分图-匈牙利算法模板
- 下一篇: 用Excel制作改进前后漏斗模型图比较