程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 【C#進階系列】07 方法,

【C#進階系列】07 方法,

編輯:C#入門知識

【C#進階系列】07 方法,


實例構造與引用類型

之前的章節其實已經寫過了引用類型的構造過程:

首先當然是,在堆中,為引用類型的實例對象分配內存,然後初始化對象的附加字段(即類型對象指針和同步塊索引)。

這個時候為對象分配的內存都是直接被置為0的,所以如果所用到的構造器中沒有對對象中的一些字段做處理,那麼這些字段的初始值都應該為0或者null。

如果一個類,沒有構造函數,那麼這個類構造的時候就會定義一個默認無參構造器,它裡面就簡單調用基類的無參構造器。

極少數的情況下,會有不實用實例構造器就能創建類型的實例的情況,比如MemberwiseClone方法(深復制)和反序列化對象時。(反序列化會調用GetUninitializedObject或GetSafeUninitializedObject方法為對象分配內存,還是後面講吧。)

還記得上一章節寫的,內聯初始化可能會導致性能問題吧:

因為每一次內聯初始化實際上都會將這些初始化字段的操作,嵌入構造函數的代碼中(注意會先進行這些操作,再進行真正的構造函數的操作)。如果只有一個構造器函數,那麼不會有什麼影響。然而如果有多個構造器函數,那麼這幾個構造器函數裡都會插入這段初始化字段的代碼。

所以當存在多個構造器參數,而代碼裡又有一大堆內聯初始化,那麼實際生成的代碼中就會有大量的冗余代碼。可以用以下方法解決:

  public class Troy{
        //不進行內聯初始化
        int _a;
        int _b;
        public Troy() {
            //將初始化過程放在某一構造參數內,一般就是無參構造函數中
            this._a = 1;
            this._b = 2;
        }
        public Troy(int i):this()  //在其它構造函數時,調用this()
        {

        }
        public Troy(int i,int j) : this()
        {

        }
    }

實例構造與值類型

值類型其實不需要定義構造器,C#編譯器也根本就不會為值類型內聯嵌入默認的無參構造器。

只有嵌套在引用類型的值類型才會被初始化為0或null,如果是基於棧的值類型,那麼在讀取之前,被要求強制初始化,否則報錯。

值類型的實例構造器只有顯式調用才有用,否則其字段都會被初始化為0或null。(也就是說,即時這個struct有無參構造函數,只要你沒有顯示調用,那麼就不會自動調用無參構造函數。實際上C#編譯器也不允許你在結構體裡寫一個無參構造函數,畢竟這個點來說,太容易誤會了)。

由於C#不允許值類型定義無參構造函數,所以值類型同樣不准內聯參數化。(靜態字段可以內聯初始化,因為是在類型對象裡面,而不是實例對象)

且值類型的任何構造函數在初始化的時候,必須對值類型的所有字段都賦值。

對於這麼麻煩的設定,當然也有解決的辦法:

    public struct Troy {
        public int a;
        public int b;
        public Troy(int i) {
            this = new Troy();//先初始化所有的字段都為0或null
            //初始化自己想玩的字段
            a = i;
        }
    }

(我不得不吐槽,我剛剛用VS自己寫了個值參數初始化的小例子,然後被360給當做病毒刪掉了。)

關於類型構造器

首先要了解到,類型構造器實際上就是構造CLR分配內存中的類型對象初始化時用的。

類型構造器是不允許有參數的,當然也就只能定義一個類型構造函數。

實際上類型構造器必須是私有的,甚至不允許顯式寫上private修飾符,這樣做正是為了防止開發人員調用。它的調用總是由CLR負責的。

簡單樣例:

class Program
    {
        static void Main(string[] args)
        {
            Troy obj = new Troy();
        }
    }
    public class Troy {
        static Troy() {
            Console.WriteLine("我就問你6不6?");
        }
    }

構造過程:

JIT編譯器編譯一個方法時,會查看代碼引用了哪些類型。任何一個類型定義了類型構造器,那麼JIT編譯器就會檢查針對當前AppDomain是否執行了這個類型構造器。是就不調用,否就調用。因為CLR希望在每個AppDomain中一個類型構造器只執行一次,所以為了不使多個線程同時調用類型構造器,在第一個調用類型構造器的線程調用時,會獲取一個互斥線程同步鎖。這樣一來,就只有一個線程可以調用了,後面的線程要用的時候發現已經調用過了,就不會再調用類型構造器了。(因為類型構造器線程安全,所以很適合在裡面初始化任何單例對象。)

雖然可以在值類型中定義類型構造函數,然而實際上因為值類型根本就不會在堆中有類型對象,所以自然裡面的代碼都不會被調用。

關於操作符重載

實際上CLR對操作符重載一無所知,因為這是編程語言的語法。

當C#這種語言寫的操作符重載語句被編譯成IL代碼時,其實已經變成了一個帶有specialname標志的函數。

當編譯器看到有+這種操作符時,就會看幾個操作數的類型中是否有定義了名為op_Addition這個函數(被編譯後的真正的函數名),而且該方法參數兼容於操作數的類型。

所以操作符重載函數中,一定要有一個參數的類型與定於這個重載方法的類型相同:

  public class Troy {
        public static int operator +(Troy a, Troy b) {
            return 10;
        }
    }

關於轉換操作符方法

  class Program
    {
        static void Main(string[] args)
        {
            Troy obj = 3;//隱式轉換成功
            string a = obj;//由於是顯示轉換重載,所以這種寫法會編譯不過
            string a =(String)obj;//顯示轉換成功
        }
    }
    public class Troy {
        // 隱式轉換操作符implicit重載
        public static implicit operator Troy(Int32 num) {
            return new Troy();
        }
        // 顯式轉換操作符explicit重載
        public static explicit operator String(Troy troy)
        {
            return "怎麼轉都是我";
        }
    }

和一般的+-這種操作符重載一樣,實際上生成的IL代碼中,換了一個名字,前綴加上了op_。

當C#編譯器檢測到代碼中一個對象期望得到另一個類型不同的對象時,就回去找這兩個類型中是否定義了隱式轉換的op_Implicit方法,是就轉。顯示類似。

可以參考Decimal類的定義去理解。

關於擴展方法

先說一下我自己的認識吧,其實我不建議使用這個東西。

因為寫得不規范的擴展方法,會增加了代碼的閱讀難度,增加維護成本。(我真的很確定有的人會把這個東西寫得到處都是)

這個東西之前在寫《重構》的學習筆記中提到過,主要用於解決別人封裝的類庫,沒法增加自己想要的函數。

第19點.不完美的類庫講到的

簡單來講,還是慎用,自己寫的類就別用擴展方法。

另外擴展方法必須是頂級靜態類中定義的靜態方法,如果是嵌套類中的話,編譯會出錯。

實際上擴展方法在C#編譯器編譯過後也只是個一般的靜態對象裡的靜態函數,只不過加了個[Extension]的特性。然而實際上這個ExtensionAttribute特性還不能在代碼中用,都是C#編譯器去自動生成的。

關於分部方法

分部方法和分部類很像,不過是方法前面加上partial修飾符。

這樣的話,如果其它分部類實現了這個方法,那麼就會加上這個方法,如果沒有實現,那麼這條代碼在編譯的時候就會被忽略。

但是分部方法只能在分部類和結構中用,且返回類型總是void,任何參數都不能用out來修飾。之所以會這樣限制,是因為方法在運行時可能就並不存在,所以也就不會有返回。

分部方法總是private的,但是C#編譯器禁止將private修飾符顯式寫在分部方法前面。(和類型構造器在這個點上類似)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved