C#泛型

frace發表於2022-03-03

為什麼會出現泛型

在泛型還沒有出來之前,使用那些為不同型別設計的方法(比如ArrayList)和只有傳參不同,其他邏輯都相同的方法時,都會想到所有型別的基類object,看似完美的解決了問題。

但是還是有大問題會出現:

1.會因為沒有記住使用的型別而出錯,造成型別不相容;

2.型別強制轉換出錯;

3.值型別和引用型別的互化即裝箱拆箱使系統效能下降。

        public void Do()
        {
            GetArrayList(1);
            GetArrayList("123");
            GetArrayList(true);
            B b = new B();
            GetArrayList(b);
        }
        public void GetArrayList(object obj)
        {
            
        }

泛型的出現很好的解決這個問題。

泛型怎麼使用

泛型有兩種形式:泛型型別(包括類、介面、委託和結構——沒有泛型列舉)和泛型方法。兩者都是表示API的基本方法(不管是指單獨的泛型方法還是完整的泛型型別),在平時期望出現一個普通型別的地方,用一個型別引數。型別引數是真實型別的佔位符。在泛型宣告中,型別引數要放在一對尖括號內,並以逗號分隔。
舉個例子C#自帶的集合類Dictionary<TKey,TValue>,例項化的時候一般都是下面這樣,在Dictionary <TKey,TValue>中,型別引數是TKey和TValue。使用泛型型別或方法時,要用真實的型別代替。string替代TKey,int替代TValue
Dictionary<string, int> dic1 = new Dictionary<string, int>();
dic1.Add("a", 1);
dic1.Add("b", 2);
Dictionary<int, bool> dic2 = new Dictionary<int, bool>();
dic2.Add(1, true);
dic2.Add(2, false);

感興趣的話可以,F12看一下Dictionary<TKey,TValue>如何宣告泛型型別,實現泛型介面,宣告無參建構函式。

這裡有個小常識:在向其他人描述泛型型別時,通常使用of來介紹型別引數或實參,因此List<T>讀作list of T。

型別的命名規範:雖然可以使用帶有T、U和V這樣的型別引數的型別,但從中根本看不出實際指的是什麼,也看不出它們應該如何使用。相比之下,像Dictionary<TKey,TValue>這樣的命名就要好得多,TKey明顯代表鍵的型別,而TValue代表值的型別。如果只有一個型別引數,而且它的含義很清楚,那麼一般使用T(List<T>就是一個很好的例子)
 
型別約束
到目前為止說的泛型可以指定任意型別,他們未做約束。但是我們在工作中經常想指定規則值接受引用型別或者值型別,從而接受有效型別的實參。
1. 引用型別約束
確保使用的型別實參是引用型別,型別實參任何類、介面、陣列、委託等
public class ReturnResult<T> where T : class
{
     List<T> ts = new List<T>();
}

2.值型別約束

確保使用的型別引數是值型別。

public class ReturnResult<T> where T : struct
{
      .......  
}

使用的話就是 ReturnResult<int> result = new ReturnResult<int>();在平時的使用中用的比較少

3.建構函式型別約束

確保型別實參有一個無參的建構函式,必須是所有型別引數最後一個約束

    /// <summary>
    /// 有無參建構函式的泛型約束
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ReturnResult<T> where T : new()
    {
        
    }
    public class Person
    {
        public string Id { get; set; }
        public string Name { get; set; }
        //增加無參的建構函式
        public Person()
        {
        }
    }
    public class Test
    {
        public void GetTest()
        {
            //例項化
            ReturnResult<Person> result = new ReturnResult<Person>();
        }
    }

4.轉換型別約束

確保一個型別的實參必須轉化為另一種型別的實參,摘自《深入理解C#》一書的一個圖片

 

 5.組合約束

應用中可能存在多個約束的可能性,當我們把不通型別的約束組合在一起時,就要考慮他們之間的共存問題了。顯然沒有一個型別即是引用型別有是值型別,這種組合就是錯誤的;如果存在多個約束組合,類應該出現在介面的前面,不能多次指向同一個介面,new()放在最後,不同的型別可以有不同的約束,分別由單獨的where引入,來看一些例子

    //有效的
    class D<T> where T : class, IDisposable, new() { }
    class E<T> where T : struct, IDisposable { }
    class F<T, U> where T : class where U : struct { }
    class G<T, U> where T : Stream where U : IDisposable { }
    //無效的
    class H<T> where T : class, struct { }
    class I<T> where T : class, Stream { }
    class J<T> where T : new(), class { }
    class M<T, U> where T : struct where U : class,T { }

協變和逆變

下節說

附錄:C#自帶的一些泛型

 

 

 

相關文章