1. 程式人生 > 其它 >泛型Generic------c#2.0新出的規則和框架的升級

泛型Generic------c#2.0新出的規則和框架的升級

1. 基本瞭解

1.1 什麼是泛型?

字面意思:不確定的型別

泛型常用:泛型方法,泛型類,泛型介面,泛型委託

1.2 泛型 T(熟悉)

T的作用,其實就是一個通用的容器,製造它的人開始不指定它是用來裝什麼的,而使用者在使用它的時候要告訴這個容器準備用來裝什麼,容器知道了用來裝什麼之後,後面所有存入操作,它都要檢查一下你放的東西是不是開始指定的東西型別

所謂泛型,即通過引數化型別來實現在同一份程式碼上操作多種資料型別

泛型允許您延遲編寫類或方法中的程式設計元素的資料型別的規範,直到實際在程式中使用它的時候,換句話說,泛型允許您編寫一個可以與任何資料型別一起工作的類或方法

泛型程式設計是一種程式設計正規化,它利用“引數化型別”將型別抽象化,從而實現更為靈活的複用。在定義泛型類時,在對客戶端程式碼能夠在例項化類時,可以用型別引數的型別種類施加限制

原理:泛型的使用是來源於c#2.0新出的規則和框架的升級,對原生需求的變更,泛型不是語法糖,是應對資料型別在傳遞引數的時候解決多程式碼冗餘問題,減少程式碼的重複和可維護性。泛型的協變與逆變和泛型約束在c#4.0出現,解決c#出現的程式碼父類繼承問題

1.3 設計思想

泛型的思想表現了一個很重要的架構思想: 延遲思想,推遲一切可以推遲的,使程式有更多的靈活性和擴充套件性,用來解決,方法中是相同的操作,但是傳入引數是不同型別的問題(例舉)

1.4 應用場景

型別不明確時:自定義物件的時候,如果我們會定義很多類似的物件,之後引數型別不同,那麼我們此時可以考慮在定義物件的時候使用泛型

定義變數,定義方法的引數,定義方法的返回值

示例:返回結果

public class Result<T>
{
    public int code { get; set; }
    public List<T> date { get; set; }
}
    
Result<A> result_a = new Result<A>() { code = 200, date = new List<A>() };
Result<B> result_b = new Result<B>() { code = 200, date = new List<B>() };

1.5 裝箱拆箱(瞭解)

在沒有泛型之前,用object型別也可以實現相同操作,但是會有些效能損耗及型別安全問題

說明

簡單來說,裝箱是將值型別轉換為引用型別 ;拆箱是將引用型別轉換為值型別

裝箱:用於在垃圾回收堆中儲存值型別。裝箱是值型別到object型別或到此值型別所實現的任何介面型別的隱式轉換

拆箱:從object型別到值型別或從介面型別到實現該介面的值型別的顯式轉換

c#型別

C#中值型別和引用型別的最終基類都是Object型別(它本身是一個引用型別)。也就是說,值型別也可以當做引用型別來處理。而這種機制的底層處理就是通過裝箱和拆箱的方式來進行,利用裝箱和拆箱功能,可通過允許值型別的任何值與Object型別的值相互轉換,將值型別與引用型別連結起來

發生場景

一種最普通的場景是,呼叫一個含型別為Object的引數的方法,該Object可支援任意為型,以便通用。當你需要將一個值型別(如Int32)傳入時,需要裝箱。

另一種用法是,一個非泛型的容器,同樣是為了保證通用,而將元素型別定義為Object。於是,要將值型別資料加入容器時,需要裝箱

裝箱和拆箱的內部操作

.NET中資料型別劃分為值型別和引用型別,與此對應,記憶體分配被分成了兩種方式,一為棧,二為堆(託管堆)

值型別只會在棧中分配,引用型別分配記憶體與託管堆(託管堆對應於垃圾回收)

2. 泛型方法

泛型方法可以定義特定於其執行範圍的泛型引數

2.1 定義泛型方法

public class MyClass
{
    // 指定MyMethod方法用以執行型別為X的引數
    public void MyMethod<X>(X x) 
    {
        //
    }

    //此方法也可不指定方法引數
    public void MyMethod<X>() 
    {
        //
    }
}

2.2 呼叫泛型方法

MyClass mycls = new MyClass();
mycls.MyMethod<int>(3);

3. 泛型類

類級別泛型引數的所有約束都必須在類作用範圍中定義

3.1 定義泛型類

簡單定義

public class MyC<T>
{
    ...
}

public class MyC<T>
{
    public T Get(){...}
    public void Show(T t){...}
}

普通類繼承泛型類

public class MyClass<T>:MyC<T>
{
    ...
}

泛型類繼承泛型類,繼承的泛型型別必須是可推斷的(與子類一致或者一個具體的型別)

public class MyClass2<T>:MyC<int>
{
    ...
}

3.2 其它示例

示例一

public class MyC<T> where T:new()
{
    public void Show<T>(){T entity}
}

public class MyClass<T> where T:IComparable<T>
{
    public void MyMethod<X>(X x,T t)
    {
        //
    }
}

4. 泛型介面

4.1 定義泛型介面

簡單定義

public interface MyInterface<T>
{
	...
}

public interface MyInterface<T>
{
	T Get();
    void Show(T t);
}

泛型介面繼承介面

public interface MyInterface2<T>:MyInterface<T>
{
	...
}

5. 泛型委託

在某個類中定義的委託可以使用該類的泛型引數

5.1 定義泛型委託

示例一

public class MyClass<T>
{
    public delegate void GenericDelegate(T t);
    public void SomeMethod(T t)
    {
 
    }
}

5.2 使用泛型委託

示例一:同定義示例一

public GenericMethodDemo()
{
    MyClass<int> obj = new MyClass<int>();
    MyClass<int>.GenericDelegate del;
    del = new MyClass<int>.GenericDelegate(obj.SomeMethod);
    del(3);
}

6. 泛型約束

6.1 型別安全問題

show方法若使用object型別引數,雖然編譯器中沒有報錯,但是在執行中會出現型別轉換失敗問題,原因是型別C並沒有繼承A父類,此處就引發了型別錯誤問題,而泛型約束就是解決型別安全問題

namespace t1
{
    class Program
    {
        static void Main(string[] args)
        {
            A obja = new A() { id = 1, name = "a" };
            B objb = new B() { id = 2, name = "b" };
            C objc = new C() { id = 3, name = "C" };

            try
            {
                Show(obja);
                Show(objb);
                Show(objc);
            }
            catch (Exception ex)
            {

            }
        }

        static void Show(object oval)
        {
            A obj = (A)oval;
            Console.WriteLine(obj.id);
        }
    }

    public class A
    {
        public int id { get; set; }
        public string name { get; set; }
    }

    public class B : A
    {

    }

    public class C
    {
        public int id { get; set; }
        public string name { get; set; }
    }

}

6.2 常用約束列表

約束說明
where T:基類名 型別引數必須是指定的基類或派生自指定的基類
where T:介面名稱 型別引數必須是指定的介面或實現指定的介面
where T:class 型別引數必須是引用型別,包括任何類、介面、委託或陣列型別
where T:struct 型別引數必須是值型別,可以指定除 Nullable 以外的任何值型別
where T:new() 型別引數必須具有無引數的公共建構函式,與其他約束同使用時,必須最後指定

6.3 常用示例

示例一:介面約束|派生約束

// 1.常見
public class MyGenericClass<T> where T:IComparable { }

// 2.約束放在類的實際派生之後
public class B { }
public class MyClass6<T> : B where T : IComparable { }

// 3.可以繼承一個基類和多個介面,且基類在介面前面
public class B { }
public class MyClass7<T> where T : B, IComparable, ICloneable { }

示例二:引用型別,值型別約束

public class c<T> where T:class
public class MyClassy<T, U> where T : class where U : struct
{
}

建構函式約束

以使用new運算子建立型別引數的例項;但型別引數為此必須受建構函式約束new()的約束。new()約束可以讓編譯器知道:提供的任何型別引數都必須具有可訪問的無引數(或預設)建構函式。new()約束出現在 where 子句的最後

// 1.常見的
public class MyClass8<T> where T :  new() { }

// 2.可以將建構函式約束和派生約束組合起來,前提是建構函式約束出現在約束列表的最後
public class MyClass8<T> where T : IComparable, new() { }

7. 泛型快取

泛型快取,使用泛型類+靜態欄位,根據不同型別的“T”會被即時編譯為不同的型別從而實現快取
個人理解,T的作用是一個型別模板,靜態欄位本身就是執行緒唯一的,使用泛型時,指定型別從而生成這個型別的副本,從而這個型別中的靜態內容也就生成了一份

7.1 示例一:簡單示例

定義泛型類,快取內容使用靜態變數並在第一次執行(使用)時快取此型別(T)內容

public class GenericClass<T>
{
    private static string GenericCache = null;
    static GenericClass() // 靜態建構函式,在泛型類第一次傳入具體的型別進來的時候,執行
    {
        GenericCache = $"{typeof(T).Name}-{DateTime.Now.ToLocalTime()}";
    }
    public static string GetData()
    {
        return GenericCache;
    }
}

測試使用,測試不同型別,或同一型別多次呼叫

static void Main(string[] args)
{
    for (int i = 0; i < 3; i++)
    {
        Console.WriteLine(GenericClass<string>.GetData());
        Thread.Sleep(1000);
        Console.WriteLine(GenericClass<int>.GetData());
        Thread.Sleep(1000);
    }
}

7.2 示例一:簡單簡版

public class GenericClass<T>
{
    // 定義為靜態只讀變數
    public static readonly string GenericCache = null;
    static GenericClass() // 靜態建構函式,在泛型類第一次傳入具體的型別進來的時候,執行
    {
        GenericCache = $"{typeof(T).Name}\t{DateTime.Now.ToLocalTime()}";
    }
}
static void Main(string[] args)
{
    for (int i = 0; i < 3; i++)
    {
        // 只能獲取,不能賦值
        Console.WriteLine(GenericClass<string>.GenericCache);
        Thread.Sleep(1000);
        Console.WriteLine(GenericClass<int>.GenericCache);
        Thread.Sleep(1000);
    }
}

7.3 示例二:實際示例

定義泛型類,用於實現快取指定SQL語句

public class GenericSql<T> where T : class
{
    public static readonly string FindSql = null;
    public static readonly string DeleteSql = null;
    public static readonly string FindAllSql = null;
    public static readonly string UpdateSql = null;
    static GenericSql()
    {
        Type type = typeof(T);
        var props = type.GetProperties().Select(x => $"{x.Name}");

        FindSql = $"select {string.Join(",", props.Select(x => $"[{x}]"))} from [{type.Name}] where id=@id";
        
        DeleteSql = $"delete from [{type.Name}] where id=@id";
        
        FindAllSql = $"select {string.Join(",", props.Select(x => $"[{x}]"))} from [{type.Name}]";
        
        UpdateSql = $"update [{type.Name}] set {string.Join(",", props.Where(x => !x.Equals("id")).Select(x => $"[{x}]=@{x}"))} where id=@id";
    }
}

測試使用,使用兩個實體類測試

public class Role
{
    public int rid { get; set; }
    public string rname { get; set; }
}
public class User
{
    public int uid { get; set; }
    public string uname { get; set; }
}
static void Main(string[] args)
{
    for (int i = 0; i < 3; i++)
    {
        Console.WriteLine(GenericSql<Role>.FindSql);
        Console.WriteLine(GenericSql<User>.FindSql);

        Console.WriteLine(GenericSql<Role>.DeleteSql);
        Console.WriteLine(GenericSql<User>.DeleteSql);

        Console.WriteLine(GenericSql<Role>.FindAllSql);
        Console.WriteLine(GenericSql<User>.FindAllSql);

        Console.WriteLine(GenericSql<Role>.UpdateSql);
        Console.WriteLine(GenericSql<User>.UpdateSql);
    }
}

8. 協變逆變(擴充套件)

協變和逆變只有在泛型介面,泛型委託中才有,協變逆變也可以組合使用

8.1 使用問題

// 鳥類
public class Bird
{
    public int id { get; set; }
}

// 麻雀類
public class Sparrow:Bird
{
    public string name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // 麻雀也屬於鳥類
        Bird bird1 = new Bird();
        Bird bird2 = new Sparrow();

        // 從人類語言上來說,一組麻雀也是一組鳥類
        // 但是在程式中,List<Bird> 是一個新的類,與 List<Sparrow> 無父子關係
        // List<Bird> list = new List<Sparrow>(); 報錯
    }
}

8.2 協變

協變:使用out修飾型別引數,且型別引數只能用作返回值,不可用於輸入引數,使得子類可在右邊

namespace t2
{
    // 鳥類
    public class Bird
    {
        public int id { get; set; }
    }

    // 麻雀類
    public class Sparrow : Bird
    {
        public string name { get; set; }
    }

    // 自定義協變
    public interface ICustomerListOut<out T>
    {
        T Get();
        //Show(T t);
    }

    public class CustomerListOut<T> : ICustomerListOut<T>
    {
        public T Get()
        {
            return default(T);
        }

        //public void Show(T t) { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 協變
            IEnumerable<Bird> birds1 = new List<Sparrow>();
			
            // 自定義
            ICustomerListOut<Bird> birds2 = new CustomerListOut<Sparrow>();
        }
    }
}

8.3 逆變

逆變:使用in修飾型別引數,且型別引數只能用作輸入引數,不可用於輸入引數返回值,使得父類可在右邊

namespace t2
{
    // 鳥類
    public class Bird
    {
        public int id { get; set; }
    }

    // 麻雀類
    public class Sparrow : Bird
    {
        public string name { get; set; }
    }

    public interface ICustomerListIn<in T>
    {
        // T Get();

        void Show(T t);
    }

    public class CustomerListIn<T> : ICustomerListIn<T>
    {
        //public T Get()
        //{
        //    return default(T);
        //}

        public void Show(T t) { ... }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 逆變
            ICustomerListIn<Sparrow> sparrow1 = new CustomerListIn<Bird>();
        }
    }
}

8.4 個人理解(不做參考)

協變:子類向父類轉換

逆變:父類向子類轉換

作者:Leo_wl     出處:http://www.cnblogs.com/Leo_wl/     本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。 版權資訊