文章出處

所有類型都繼承自System.Object

System.Object的基本方法

名稱 訪問級別 說明
Equals() public 如果兩個對象有相同的值就返回true
GetHashCode() public 返回對象的值的哈希碼
ToString() public 默認返回類型的完整名稱(this.GetType().GetFullName)
GetType() public 指出調用GetType()方法的對象的類型
MemberwiseClone() protected 可以創建類型的一個新的實例,并將新對象的實例字段與this設置一致,返回的是新實例的引用

new操作符

CLR要求所有對象都是通過new來創建的,如下

 Book book = new Book("語文");

new操作符創建對象的過程:
1.計算類型及其所有基類類型(直到Object)中定義的所有實例字段需要的字節數;堆上的對象成員還需要計算“類型對象指針”和“同步塊索引”的字計數,這些成員由CLR管理。
2.從托管堆中分配類型要求的字節數,分配的所有字節都設置為0。
3.初始化對象的“類型對象指針”和“同步快索引”。
4.對用類型的實例構造器,向其傳入在對new的調用中指定的任何參數(上面代碼中傳入的“語文”);編譯器會在構造函數中生成代碼對基類構造器進行調用,每個類型的構造器在調用時,都負責初始化由這個類型定義的實例字段;由于Object類型沒有實例字段,該構造器只是簡單的返回,并沒有任何操作。
執行完new操作后,會返回一個新建的對象引用(或指針),如上面的代碼將這個引用保存到了變量book中。

類型轉換

CLR的一個重要特性就是類型安全,在運行時,總是知道一個對象是什么類型。
CLR允許將一個對象轉換為它的派生類型和基類型;向基類型轉換時是安全的隱士轉換,向派生類轉換時有可能在運行時會失敗,所以只能進行顯示轉換。

 //Object是Teller的基類,直接隱式轉換
 object o = new Teller();
 //需要強制轉換
 Teller teller = (Teller)o;

使用is和as操作符來轉型

is檢查對象是否兼容于制定類型,返回一個布爾值;is操作符永遠不會拋出異常,即使對象引用的是null。

  if (obj is Teller)
  {
      Teller teller = (Teller)obj;
  }

上面的代碼是is的常用操作方式,CLR會對對象進行兩次類型檢查,雖然這樣增強了類型的安全性檢查,但無疑浪費了性能;為了避免這個問題,可以通過as的方式進行轉型。

  Teller teller = obj as Teller;
  if (teller != null)
  {
  }

CLR會檢測obj是否兼容于Teller,如果是,as返回一個非null的引用,如果不兼容,as操作符將返回null

命名空間和程序集

命名空間用于對相關類型進行邏輯性的分組,如System.Text定義了一組字符串處理的類型

System.Text.StringBuilder builder = new System.Text.StringBuilder();

顯然,這樣寫代碼非常繁瑣;C#編譯器通過使用using減少了輸入量。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TypeBasis
{
    class Program
    {
        static void Main(string[] args)
        {
            StringBuilder s = new StringBuilder();
        }
    }
}

對于編譯器來說,命名空間的作用就是使類型更具有唯一性。當兩個類型名稱發生沖突,為了消除歧義,必須顯示的告訴編譯器使用的是哪一個類型。使用using還可以創建類型或者命名空間的別名。

using System;
using ST = System.Text;

namespace TypeBasis
{
    class Program
    {
        static void Main(string[] args)
        {
            ST.StringBuilder builder = new ST.StringBuilder();
        }
    }
}

CLR不知道命名空間的任何類型,訪問一個類型時,CLR需要知道類型的完整名稱。
當兩個類型的命名空間及類型名稱都一樣的時候,可以通過外部別名消除歧義;為了避免發生這種問題,應該常使用公司名稱(而不是公司名稱首字母縮寫或者其他簡寫)來作為自己的頂級命名空間名稱。
namespace指令的作用是告訴編譯器為源代碼中出現的每個類型名稱附加命名空間名稱前綴,減少程序員的輸入量。
命名空間和程序集之間不一定是相關的,同一個命名空間也有可能在不同的程序集中。

運行時的相互聯系

通過下面的代碼來了解運行時間的相互關系

    class Program
    {
        static void Main(string[] args)
        {
            //當程序運行時,并即將調用到上面的`Main`中的`DoSomething()`方法時,首先JIT(即時編譯器)將`DoSomething()`的IL(中間代碼)轉換成CPU指令,
            //并確保方法中的定義了的類型的程序集都已經加載,利用程序集的元數據,CLR創建一些結構數據來表示類型本身;在每個對象中,最后都會包含一個方法表,類型中定義的每個方法都有一個對應的記錄
            //當`DoSomething()`方法開始執行時,會自動為方法中的局部變量初始化值(`null`或者0),如果未顯示為局部不變量初始化,調用的時候將引發“使用了未賦值的局部變量”異常    
            DoSomething();
        }

        static void DoSomething()
        {
            Employee e;
            int year;
            //創建Manager類型的一個實例,并分配實例所需要的內存(實例字段及所有基類的實例字段所需的字節數),并將實例字段設置為null或者0
            //然后再調用構造函數(構造函數的本質是修改實例字段的一個方法)
            //new操作符將Manager對象的內存地址返回,并保存在變量e中
            e = new Manager();
            //調用靜態方法時,CLR會定位定義靜態方法的類型對應的類型對象,然后JIT在類型對象中查找與被調用的方法相關的記錄,對方法JIT編譯,再調用JIT編譯的代碼
            //e不再引用上面的Manager對象,它將被GC回收,GC回收后將釋放它所占用的內存,e將保存由Employee.Lookup("Joe")返回來的一個新的地址
            e = Employee.Lookup("Joe");
            //調用Employee類型實例的GetYearEmployed方法,如果Employee中沒有定義GetYearEmployed方法,JIT編譯器會沿著Employee的基類查找,知道Object
            year = e.GetYearEmployed();
            //如果Employee.Lookup("Joe")返回的是一個Manager引用,將調用Manager類型中的GenProgressReport重寫方法,如果返回的是Employee類型引用,調用的就是Employee中的虛方法
            e.GenProgressReport();
        }
    }

    public class Employee
    {
        public int GetYearEmployed()
        {
            //TODO: do something...
            return default(int);
        }
        public virtual string GenProgressReport()
        {
            //TODO: do something...
            return default(string);
        }
        //靜態成員所需的字節包含在對象本身
        public static Employee Lookup(string name)
        {
            //TODO: do something...
            return default(Employee);
        }
    }

    public class Manager : Employee
    {
        public override string GenProgressReport()
        {
            //TODO: do something...
            return default(string);
        }
    }

CLR創建類型對象時,必須初始化“類型對象指針”成員;CLR開始在一個進程中運行時,會立即為MSCorLib.dll定義的System.Type類型創建一個特殊的類型對象。ManagerEmployee都是該對象的“實例”,因此,它們的對象指針成員會初始化為對System.Type的類型對象的引用;當然System.Type的“類型對象指針”成員指向的是它本身。
System.ObjectGetType()方法返回的是存儲在制定對象的“類型對象指針”成員中的地址,即指向對象的類型對象的一個指針,這樣就可以知道系統中任何對象的類型。

參考:《CLR via C#》


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()