C#泛型

    在C#開發中,必不可少的要用到泛型。泛型是.NET2.0版本就有的,它普遍應用於C#框架中容器的使用中。下面咱們來詳細介紹一下。數據庫

  1、泛型的主要優點編程

    1.性能更高。緩存

    2.類型更安全。
安全

    3.代碼更多的重用和擴展性。框架

  2、泛型的基本使用ide

    泛型的一個主要優勢是性能,咱們來看下面的例子:函數

   static void Main(string[] args)
        {
            //不是泛型的集合類
            ArrayList list = new ArrayList();
            //添加一個值類型 裝箱操做
            list.Add(12);
            //去除第一個元素12 拆箱操做
            int num = (int)list[0];
            Console.WriteLine(num);
            Console.WriteLine("執行結束");
            Console.ReadKey();
        }
    //
        // 摘要:
        //     將對象添加到 System.Collections.ArrayList 的結尾處。
        //
        // 參數:
        //   value:
        //     要添加到 System.Collections.ArrayList 末尾的 System.Object。該值能夠爲 null。
        //
        // 返回結果:
        //     value 已添加的 System.Collections.ArrayList 索引。
        //
        // 異常:
        //   T:System.NotSupportedException:
        //     The System.Collections.ArrayList is read-only.-or- The System.Collections.ArrayList
        //     has a fixed size.
        public virtual int Add(object value);
元數據中ArrayList類的Add方法

    相信你們都知道,裝箱拆箱是比較損耗性能的,在執行add方法是, 把值類型轉換成引用類型(裝箱),取出來時在去拆箱,那怎麼樣才能避免這種狀況發生呢?性能

再來看下面代碼:測試

           //泛型集合類
            List<int> list = new List<int>();
            list.Add(12);
            int num = list[0];
            Console.WriteLine(num);
            Console.WriteLine("執行結束");
            Console.ReadKey();    

    這個時候,代碼並無發生裝箱拆箱操做。spa

 //
        // 摘要:
        //     將對象添加到 System.Collections.Generic.List`1 的結尾處。
        //
        // 參數:
        //   item:
        //     要添加到 System.Collections.Generic.List`1 末尾的對象。對於引用類型,該值能夠爲 null。
        public void Add(T item);
元數據中List類的Add方法

    代碼寫到這裏時,咱們只是建立了一個List泛型集合,你可能還感受不到泛型優點到底在哪裏,你也可能不知道泛型究竟是怎麼用的。好,下面咱們寫個測試還有本身實際應用的例子。

static void Main(string[] args)
        {
            //不是泛型的集合類
            ArrayList arylist = new ArrayList();
            //添加一個值類型 裝箱操做

            //泛型集合類
            List<int> list = new List<int>();


            //計時類
            Stopwatch watch = new Stopwatch();
            watch.Start();//開始計時
            for (int i = 0; i < 10000000; i++)
            {
                arylist.Add(i);
            }
            watch.Stop();
            Console.WriteLine("Arraylist用時:"+watch.ElapsedMilliseconds);

            Stopwatch watch1 = new Stopwatch();
            watch1.Start();//開始計時
            for (int i = 0; i < 10000000; i++)
            {
                list.Add(i);

            }
            watch1.Stop();
            Console.WriteLine("list用時:" + watch1.ElapsedMilliseconds);
            Console.WriteLine("執行結束");
            Console.ReadKey();
        }
測試arraylist和list集合的性能

    執行結果:

 

 

     以上的例子中,能夠看出在計時的過程當中代碼寫的重複了, 怎麼解決這個問題呢, 泛型要排上用場了。

    咱們想一下,相同的操做(都是循環添加元素,計算用時)用同一個方法實現不就ok了,只不過這個方法是泛型的(能夠接受ArrayList,也能夠接受List),下面咱們來寫一下這個方法。   

        //咱們用T來表明泛型
        public static long GetTime<T>(T t)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();//開始計時
            for (int i = 0; i < 10000000; i++)
            {
                t.Add(i);
            }
            watch.Stop();
            return watch.ElapsedMilliseconds;
        }    

    可是問題來了, 這裏並無Add方法讓咱們使用啊。 咱們的思路是把這個T在調用時能夠看成ArrayList類型, 也能夠看成List類型,這裏就設計到了泛型約束。

  3、泛型約束

    若是使用泛型時, 想要調用這個泛型類型中的方法, 那麼就須要添加約束。泛型約束主要有如下幾種:

    

約束 說明
where T:struct 對於結構的約束, T必須是值類型
where T:class T必須是引用類型
where T:ITest T必須實現了ITest接口
where T:Test T必須繼承基類Test
where T:new() T必須有默認構造函數
where T:T2 T派生自泛型類型T2,也稱爲裸類型約束

 

       

 

 

 

 

 

 

    咱們接着上個泛型方法來修改,ArrayList和List都實現了接口IList , 這個時候咱們加上這個接口約束;

        //咱們用T來表明泛型
        public static long GetTime<T>(T t)where T:IList
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();//開始計時
            for (int i = 0; i < 10000000; i++)
            {
                t.Add(i);
            }
            watch.Stop();
            return watch.ElapsedMilliseconds;
        }    

調用結果:

 

     代碼寫到這裏時,相信你已經對泛型有所瞭解了,可是真要應用到本身之後的邏輯編程中時,必定要善於總結:相同類型的相同方法,或者業務邏輯相同,只有某個判斷不一樣時,能夠用上泛型,不只高效還代碼量小。

  4、應用場景示範

    在咱們的項目開發中,數據庫的增刪改查確定是少不了的, 在這裏咱們用泛型來定義增刪改查的泛型類。 以後創建一個用戶表(實際應用中對應數據庫中表結構),數據庫中每個表均可以用泛型類中定義的方法, 不須要每個都寫增刪改查操做,也是面向對象編程的一種思想:

public class BaseDal<T>where T:class ,new ()
    {
        //如下是增刪查改示範
        public void Query(int id) {
            Console.WriteLine(typeof(T).Name+"查詢方法,id="+id);
        }
        
        public void Update(T t) {

            Console.WriteLine(typeof(T).Name+"更新方法");
        }
        public void Delete(T t)
        {
            Console.WriteLine(typeof(T).Name + "刪除方法");
        }

        public void Add(T t) {
            Console.WriteLine(typeof(T).Name + "添加方法");
        }
    }
public   class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
user 類
           BaseDal<User> dal = new BaseDal<User>();
            var user = new User()
            {
                Id = 0,
                Name = "用戶1"
            };
            dal.Add(user);
            dal.Query(0);
            user.Name = "用戶11";
            dal.Update(user);
            dal.Delete(user);

            Console.ReadKey();
調用示範

  5、泛型的協變和抗變

    協變和抗變主要是對參數和返回值的類型進行轉換,在.NET4以後能夠經過協變和抗變爲泛型接口或這泛型委託添加這個擴展。

    如今咱們寫倆個類Shape(形狀)、Rectangle(矩形類),Rectangle派生自Shape,寫一個方法public static Rectangle GetRec() ;這個時候你會發現, 方法的泛型類型是抗變的, 就是我返回一個類型爲Rectangle可是我能夠用Shape來接收, 但泛型在NET4.0以前不支持這個方式, 泛型在NET4.0以後提供了支持泛型接口和泛型委託的協變和抗變。

  //形狀
  public  class Shape
    {
        public double Width { get; set; }
        public double Height { get; set; }

        public override string ToString()
        {
            return string.Format("width:{0},height:{1}",Width,Height);
        }
    }

    //矩形
    public class Rectangle : Shape {

    }

///-----------------------------------方法與調用

public static Rectangle GetRec() {
            return new Rectangle();
        }
 Shape s = GetRec();
            Console.ReadKey();
普通方法抗變代碼說明

    一、泛型接口的協變

      泛型接口在類型T前加上out關鍵字,這個時候泛型接口就是協變的,也就意味着返回類型只能是T。 直接看代碼:

 //泛型接口的協變
    public interface IIndex<out T>
    {
        T GetT(int index);
        int Count { get; }
    }


    public class RecCollection : IIndex<Rectangle>
    {
        private Rectangle[] data = new Rectangle[2] {
            new Rectangle() { Width=10,Height=20 },
            new Rectangle() {Width=20,Height=30 }
        };


        public int Count
        {
            get
            {
              return  data.Count();
            }
        }

        public Rectangle GetT(int index)
        {
            return data[index];
        }
    }
//調用         
        Shape s1 = new RecCollection().GetT(1);
            Console.WriteLine(s1.ToString());

            IIndex<Rectangle> rec = new RecCollection();
            IIndex<Shape> shapes = rec;
            for (int i = 0; i < shapes.Count; i++)
            {
                Console.WriteLine(shapes.GetT(i));
            }
            Console.ReadKey();

以上代碼能夠看出, 咱們把泛型接口的泛型定義爲矩形(Rectangle), 可是在接受的時候能夠用基類(Shape) ,在這裏實現了協變。

      2.泛型接口的抗變

        若是泛型類型用in關鍵字標註,那麼這個泛型接口就是抗變的,這樣,接口只能把泛型類型T用做其方法的輸入。 

//泛型接口的抗變
    public interface IDisplay<in T>
    {
        void Show(T item);
    }

    public class ShapeDisplay : IDisplay<Shape>
    {
        public void Show(Shape item)
        {
            Console.WriteLine(item);
        }
    }
//-------------------------如下調用------
  Rectangle recs = new Rectangle() { Width=100,Height=200};
  IDisplay<Shape> shapeDisplay = new ShapeDisplay();
  shapeDisplay.Show(recs);

  IDisplay<Rectangle> recDisplay = shapeDisplay;
  recDisplay.Show(recs);
  Console.ReadKey();

以上代碼能夠看出泛型也是支持抗變和協變的。

    6、總結

      泛型是C#語言發展帶來的新方法,以上例子只是簡單的運用,但願你們能經過以上例子有所啓發,能在項目中更好的使用泛型。以上還有泛型緩存沒有說到,你們有興趣能夠找下資料,今天就到這裏吧, 沒有說到的還有很差的地方, 歡迎你們指正!

 

相關文章
相關標籤/搜索