程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> CLR筆記:8.方法

CLR筆記:8.方法

編輯:關於.NET

1.實例構造器ctor(引用類型)

創建引用類型的實例時的步驟:

首先,為實例的數據字段分配內存;

接著,初始化對象的系統開銷字段(類型對象指針和同步塊索引);

最後,調用類型的實例構造器設置對象的初始狀態。

ctor不能被繼承,不能用virtual,new,override,sealed,abstract。

如果類中沒有顯示定義任何ctor,則默認定義一個無參ctor,這個ctor不執行任何語句,只是調用基 類的無參ctor;當然,如果類中有ctor,則不存在這個默認的無參ctor。

如果類為abstract的,則默認的ctor是protected;否則這個默認ctor都是public的。

如果類為static的,則不會生成默認的ctor。

如果基類A中沒有提供無參ctor,這裡,我們只考慮A中只有一個有參ctor(如果連這個ctor也沒有, 那麼A就是上面的那種情況了),這時,子類B必須顯示調用基類的ctor,否則不能編譯,如下所示:

public class A

    {
        public A(int t)

        { 
        }

    }
    public class B : A

    {

        public B(int t)  : base(t)        //必須顯示調用

        {
        }

    }

最終,都會調用到System.Object的公有無參ctor。

不需要實例構造器的時候:反序列化;Object.MemberwiseClone()方法。

內聯方法,其實就是在默認無參ctor中賦值。

2.實例構造器ctor(值類型)

1.C#中,struct不能包含顯示的無參ctor——CLR允許struct有無參ctor

2.struct一定要顯示調用其有參ctor,這樣在new新對象的時候,才會執行ctor中的語句

3.CLR會保證所有實例字段(值類型和引用類型)都被初始化為0或者null (如果沒有手動設置,就由CLR 自動分配)。

4.在struct中,不能使用內聯直接給字段賦值(初始化只能在顯示ctor中做)

5.每個字段都要在ctor中初始化,不然編譯器會報錯

3.靜態構造器cctor

cctor可以用於接口/引用類型/值類型,但是C#不支持接口。

cctor位於AppDomain中,在類第一次被訪問時執行。

cctor不能超過一個(可以沒有),而且永遠沒有參數。

cctor是私有的,但不能顯示聲明為私有。

在值類型中定義cctor是沒有意義的——沒有機會執行cctor。

cctor的調用過程:

編譯時,JIT編譯器將檢查AppDomain是否執行了cctor(有記錄的),以決定是否生成調用代碼;

執行時,如果有多個線程,則需要一個互斥的線程同步鎖,以確保只執行一次cctor。

避免在兩個類的cctor中互相引用,CLR不能確保先執行哪一個cctor,可以編譯,但是返回指不唯一。

如果cctor拋出異常,CLR會認為該類型不可用,與此類型相關的操作都會拋出 System.TypeInitializationException異常。

cctor中只能訪問靜態字段,它的用途就是初始化這些靜態字段。也可以使用內聯初始化,與cctor效 果一樣。

cctor不應調用其基類的cctor,兩者沒有關系。

CLR不支持靜態的Finalize()方法。

cctor的性能:

精確語義Precise:針對在cctor中初始化而言;

字段初始化前語義BeforeFieldInit:針對於內聯初始化而言。

二者的區別在於是否會在MSIL中的cctor上附加一個BeforeFieldInit標記。

測試代碼如下:

class Program
    {
        static void Main()
        {
            const Int32 iterations = 1000 * 1000 * 1000;

            PrefTest1(iterations);

            PrefTest2(iterations);
            Console.ReadLine();

        }
        private static void PrefTest1(Int32 iterations)

        {

            Stopwatch sw = Stopwatch.StartNew();

            for (Int32 i = 0; i < iterations; i++)

            {
                BeforeFieldInit.s_x = 1;

            }

            Console.WriteLine("PrefTest1: {0} BeforeFieldInit", sw.Elapsed);
            sw = Stopwatch.StartNew();

            for (Int32 j = 0; j < iterations; j++)

            {

                Precise.s_x = 1;

            }

            Console.WriteLine("PrefTest1: {0} Precise", sw.Elapsed);

        }
        private static void PrefTest2(Int32 iterations)

        {

            Stopwatch sw = Stopwatch.StartNew();

            for (Int32 i = 0; i < iterations; i++)

            {

                BeforeFieldInit.s_x = 1;

            }

            Console.WriteLine("PrefTest2: {0} BeforeFieldInit", sw.Elapsed);
            sw = Stopwatch.StartNew();

            for (Int32 j = 0; j < iterations; j++)

            {

                Precise.s_x = 1;

            }

            Console.WriteLine("PrefTest2: {0} Precise", sw.Elapsed);

        }

    }

*Stopwatch類,提供一組方法和屬性,可以准確地測量運行時間

4.操作符重載

CLR不知道操作符是什麼;操作符是C#定義的,相應的生成編譯器識別的語言。

操作符重載是public static的,有一元重載和二元操作兩種方式。

要求重載方法的參數至少有一個參數與重載方法的類型一樣。

運算符參數不能使用ref/out修飾符。

例子:對於運算符+,

在定義時,會在編譯器中生成op_Addition()方法,在方法定義表中,這個方法屬於specialname組— —說明它是一個特殊方法

在調用時,編譯器會在specialname組查找相應的op_Addition()方法,如果存在而且方法參數匹配, 則執行;否則,編譯錯誤。

對於操作符重載,建議同時定義一個友好的方法,如重載+,同時定義一個Add方法。

操作符語法詳見http://www.cnblogs.com/Jax/archive/2007/09/13/891984.html

5.轉換操作符方法

System.Decimal是一個很好的學習操作符重載/轉換的Sample

為一個Rational定義轉換符構造器和方法:

操作符轉換也是public static的

public sealed class Rational

    {

        public Rational(Int32 num)

        {

            //由Int32構建一個Rational

        }


        public Rational(Single num)

        {

            //由Single構建一個Rational

        }


        public Int32 ToInt32()

        { 

            //將Rational轉換為Int32

        }


        public Single ToSingle()

        {

            //將Rational轉換為Single

        }


        //將Int32隱式轉為Rational,回調Rational(Int32)構造器

        public static implicit operator Rational(Int32 num)

        {

            return new Rational(num);

        }


        //將Single隱式轉為Rational,回調Rational(Single)構造器

        public static implicit operator Rational(Single num)

        {

            return new Rational(num);

        }


        //將Rational顯式轉為Int32,回調ToInt32()

        public static explicit operator Int32(Rational r)

        {

            return r.ToInt32();

        }


        //將Rational顯式轉為Single,回調ToSingle()

        public static explicit operator Single(Rational r)

        {

            return r.ToSingle();
        }
    }

由上面代碼可以看出,implicit/explicit是對兩個構造器和兩個ToXXX()方法的包裝。

相應的IL代碼:

public static Rational op_Implicit(Int32 num)

        public static Rational op_Implicit(Single num)

        public static Int32 op_Explicit(Rational r)

        public static Single op_Explicit(Rational r)

第3個和第四個方法僅僅是返回類型不同,這樣的語法只有在IL中允許。可以看到,操作符轉換實際上 是在利用IL的這一特性。

6.通過引用向方法傳遞參數

默認CLR的方法參數都是按值傳遞的。無論引用還是值類型參數,都是傳遞一個copy的副本——這樣意 味著方法可以修改對象,而不對方法外的對象有影響,僅在方法內部有影響。

CLR允許按照引用方式傳遞參數:out,ref,在聲明和調用時都要加上ref/out關鍵字

二者在CLR中等效,在C#中有區別:

不能將未初始化的參數作為ref傳遞到方法

//正確使用ref

static void Main()

        {

            int x = 2;


            GetRef(ref x);

        }


        private static void GetRef(ref int x)

        { 

            x ++;

        }

//錯誤使用ref

static void Main()

        {

            int x;  //未初始化,即使方法中賦值也不行


            GetRef(ref x);

        }


        private static void GetRef(ref int x)

        { 

            x = 10;

        }

在out方法中必須給out參數初始化賦值,這時,無論調用前是否給out參數初始化賦值,out方法中都 要賦值,否則會編譯錯誤。示例如下:

//正確使用out

        static void Main()

        {

            //以下兩句話都是對的

            int x;

            int x = 10;


            GetOut(out x);

        }


        private static void GetOut(out int x)

        { 

            x = 10;     //一定要初始化,即使調用前已經初始化了

        }

        //錯誤使用out

        static void Main()

        {

            int x = 10;


            GetOut(out x);

        }


        private static void GetOut(out int x)

        { 

            x ++;     //一定要初始化,即使調用前已經初始化了

        }

方法可以基於ref/out可以重載,ref/out也算是方法簽名的一部分。但是重載不能僅限於ref和out的 差別,如下:

可以有

public sealed class Point

    {

        static void Add(Point p);

        static void Add(ref Point p);

    }

不能有

public sealed class Point

    {

        static void Add(Point p);

        static void Add(ref Point p);

        static void Add(out Point p);

    }

用ref實現交換兩個引用類型的方法:標准寫法,使用泛型

public static void Swap<T>(ref T a, ref T b)

        {

            T t = b;

            b = a;

            a = t;

        }


        public static void SomeMethod()

        {

            String s1 = "Jax.Bao";

            String s2 = "Fish.Xu";


            Swap(ref s1, ref s2);

        }

out/ref在值類型上使用和引用類型上使用行為相同。僅差在值類型實例分配到的是內存,引用類型分 配到的是指針。

以後的FileStream可以寫成以下模式:

static void Main()

        {

            FileStream fs = null;


            ProcessFiles(ref fs);


            for (; fs != null; ProcessFiles(ref fs))

            { 

                fs.Read(.);            

            }

        }



        private static void ProcessFiles(ref FileStream fs)

        {

            if (fs != null)

            {

                fs.Close();

            }


            if (noMoreFileToProcess)

            {

                fs = null;
            }

            else

            { 

                fs = new FileStream(.);

            }

        }

7.向方法傳遞可變數量的參數 params關鍵字

只有最後一個參數可以是params的,而且必須是一個一維數組,可以傳遞null或0個元素的數組,甚至 是不傳值(忽略此參數,會默認生成0長的數組作為方法參數)。

調用的時候,不用創建數組對象,直接在方法中傳入任意數量的參數。

static void Main()
{
Console.WriteLine(TestParams());
Console.WriteLine(TestParams("1", "2"));
}
public static int TestParams(params string[] p1)
{
return p1.Length;
}

8.聲明方法的參數類型

原則1:方法參數盡可能指定為最弱的類型

選用IEnumberable<T>,而不是IList<T>或ICollection<T>

原則2:方法返回類型盡可能指定為最強的類型

返回FileStream,而不是Stream

原則3:如果希望在不影響調用代碼的情況下,能對方法內部實現進行修改,這時候返回類型要使用最 弱類型中的最強的一個。

使用IList<T> ,而不是List<T>

9.CLR不支持常量方法和常量參數

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