香蕉视频app

Keep on going never give up.

Let's Go

C# 學習筆記(55)全面解析泛型

C#Lonely2019-06-10 18:28:19113次0條

類型參數

根據泛型類型參數是否提供實際數據,又可把泛型分為兩類:未綁定的泛型已構造的泛型如果沒有為類型參數提供實際數據,此時的泛型被稱為未綁定的泛型;而如果已指定了實際數據類型作為參數,則此時的泛型被稱為已構造的泛型。

已構造的泛型又可分為開放類型密封類型。其中,開放類型是指包含類型參數的泛型,而封閉類型則是指那些已經為每一個類型參數都傳遞了實際數據類型的泛型。

//示例:
//Dictionary<,> 開放類型
Type t = typeof(Dictionary<,>);
Console.WriteLine(t.ContainsGenericParameters); //true
//Dictionary<int,string> 封閉類型
t = typeof(Dictionary<int,string>);
Console.WriteLine(t.ContainsGenericParameters); //false
Console.ReadKey();

香蕉视频app 以上代碼首先用typeof操作符獲得了類型聲明,然后通過Type.ContainsGenericParameters屬性來判斷類型對象是否包含未被實際類型替代的類型參數。如果存在未替代的類型參數,則返回true,表明此時泛型類型為開放類型;否則為封閉類型。


泛型類型的靜態字段和靜態函數問題

靜態數據類型是屬于類型的,對于靜態字段來說,如果在每個MyClass類中定義了一個靜態字段x,則不管之后還創建了多少個該類的實例,也不管從該類派生出多少個實例,都只存在一個MyClass.x字段。但泛型類型卻并非如此,每個封閉的泛型類型中都有僅屬于它自己的靜態數據。

//泛型類
public static class Test<T>
{
    public static string field; //靜態字段
    public static void Print() //靜態函數
    {
        Console.WriteLine(field+" :"+typeof(T).Name);
    }
}

//非泛型類
public static class MyClass
{
    public static string field; //靜態字段
    public static void Print() //靜態函數
    {
        Console.WriteLine(field);
    }
}

class Program
{
    static void Main(string[] args)
    {
        //示例:
        //非泛型類
        MyClass.field = "張三";
        MyClass.field = "李四";
        MyClass.field = "鉆石王老五";
        MyClass.Print();

        //泛型類
        Test<int>.field= "一";
        Test<int>.Print();

        Test<string>.field = "二";
        Test<string>.Print();

        Test<object>.field = "二";
        Test<object>.Print();

        Console.ReadKey();
    }
}

運行結果:

image.png

從以上結果可以看出,每個封閉的泛型類型都有屬于它自己的靜態字段。這是因為,在使用實際類型參數代替泛型參數時,編譯器會根據不同的類型實參,重新生成類型。對于編譯器來說,每個封閉泛型類型都是一個不一樣的類型,所以它們都有屬于它們自己的靜態字段。對于靜態構造函數,道理也是如此,每個封閉的泛型類型都會有一個靜態構造函數。每個封閉泛型類型都會調用其構造函數,對于非泛型類型,其構造函數只會調用一次。


類型參數的推斷

香蕉视频app 由于使用泛型時都需要寫“<”和“>”等符號,在閱讀代碼時,一旦代碼變多,難免令開發人員感覺頭暈。通過使用編譯器的類型推斷,你便可以在寫泛型代碼時省略掉這些符號,具體的實際類型則交由編譯器自行推斷。

class Program
{
    static void Main(string[] args)
    {
        //示例:
        int n1 = 10;
        int n2 = 20;

        //不使用類型推斷的代碼
        Tset<int>(ref n1, ref n2);
        //使用類型推斷的代碼
        Tset(ref n1, ref n2);
        Console.WriteLine("n1值:"+ n1);
        Console.WriteLine("n2值:" + n2);

        //無法推斷出方法的類型參數
        string str = "string";
        object obj = "object";
        Tset(ref str, ref obj);

        Console.ReadKey();
    }

    public static void Tset<T>(ref T t1, ref T t2)
    {
        T temp = t1;
        t1 = t2;
        t2 = temp;
    }
}

在以上代碼中,編譯器會根據傳遞的方法實參來判斷傳入的實際類型參數。如果編譯器根據傳入的參數不能推斷出實際參數類型,就會出現編譯時錯誤。使用類型推斷后,可以省略“<”和“>”,從而在泛型代碼變多時,增強代碼可讀性。類型推斷只能用于泛型方法,它對泛型類則并不適用,因為編譯器不能通過泛型類的構造函數推斷出實際的類型參數。


類型參數約束

類型參數約束就是限制類型參數只能代表某些符合要求的類型。類型約束用where香蕉视频app關鍵字來限制某個類型實參的類型。如以下代碼中的where T: IComparable<T>語句就使類型參數繼承于IComparable<T>接口,這樣就可以保證傳入的類型實參都具有CompareTo方法了。

//比較兩個值的大小,返回大的那個
public static T GetMax<T>(T obj1, T obj2) where T:IComparable<T>
{
    if (obj1.CompareTo(obj2) > 0)
    {
        return obj1;
    }
    return obj2;
}

C#中有4種約束可以使用,它們的語法類似:約束要放在泛型方法或類型聲明的結尾,并且使用where關鍵字。


引用類型約束

引用類型約束表示形式為T:class,它確保傳遞的類型實參必須是引用類型。注意:約束的類型參數和類型本身沒有關系,即在定義一個泛型結構體時,泛型類型一樣可以被約束為引用類型。此時,結構體類型本身是值類型,而類型參數約束為引用類型,它可以為任何的類、接口、委托或數組等,但不能指定以下這些特殊的引用類型:System.Object、System.Array、System.Delegate、System.MulticastDelegate、System.ValueType、System.Enum和System.Void。

代碼示例:

public class SampleReference<T> where T : Stream
{
    public void Test(T stream)
    {
        stream.Close();
    }
}

在以上代碼中,類型參數T設置了引用類型約束。where T:Stream的意思就是告訴編譯器:傳入的類型必須是System.IO.Stream,或者是從Stream派生的一個類型。如果一個類型參數沒有指定約束,則默認T為System.Object類型。但若你在代碼中顯式地指定了System.Object約束,則編譯器會報錯:約束類型不能是特殊類object。


值類型約束

值類型約束的表示形式為T : struct它確保傳遞的類型實參是值類型(包括枚舉),但這里的值類型不包括可空類型。

香蕉视频app 代碼示例:

public class SampleReference<T> where T : struct
{
    public static T Test()
    {
         return new T();
    }
}

在上面的代碼中,new T()是可以通過編譯的,因為T是一個值類型,而所有值類型都有一個公共的無參構造函數。但如果不對T進行約束,或約束為引用類型,則上面的代碼就會報錯,因為有的引用類型是沒有公共的無參數構造函數的。


構造函數類型約束

構造函數類型約束的表示形式為T:new(),如果類型參數有多個約束,則此約束必須最后指定。構造函數類型約束確保指定的類型實參有一個公共無參構造函數的非抽象類型。這適用于所有值類型,所有非靜態、非抽象、沒有顯式聲明構造函數的類,以及顯式聲明了一個公共無參構造函數的所有非抽象類。這里需要注意,如果同事聲明指定構造器約束和struct約束,C#編譯器會認為這是一個錯誤。因為這樣指定是多余的,所有值類型都會隱式地提供了一個無參公共構造函數。就如同定義接口時指定訪問類型為public一樣,編譯器會報錯,因為接口一定是public的,編譯器認為這樣做是多余的。


轉換類型約束

轉換類型約束的表示形式為T:基類名、T:接口名、T:U。T:基類名確保指定的類型實參必須是基類或派生自基類的之類;T:接口名確保指定的類型實參必須是接口或實現了該接口的類;而T:U則確保T提供的類型實參必須是U提供的類型實參或派生于U提供的類型實參,即前面一個類型實參必須是后面的類型實參或后面類型實參子類。

轉換約束示例

image.png


組合約束

組合約束是將多個不同種類的約束合并在一起的情況。這里需要注意,沒有任何一種類型即是引用類型又是值類型,所以引用約束和值約束不能同時使用如果存在多個轉換類型約束,且其中一個是類,則類必須放在接口前面。不同的類型參數可以有不同的約束,但每種類型參數必須分別使用一個單獨的where關鍵字。

組合約束示例

image.png

暗錨,解決錨點偏移

文章評論

    嘿,來試試登錄吧!