第一課《.net之--泛型》

来源:https://www.cnblogs.com/MachineVision/archive/2019/02/25/10431199.html
-Advertisement-
Play Games

今天我來學習泛型,泛型是編程入門學習的基礎類型,從.net誕生2.0開始就出現了泛型,今天我們開始學習泛型的語法和使用。 什麼是泛型? 泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.NET框架引入了類型參數(type parameters)的概念。類型參數使得 ...


 

  今天我來學習泛型,泛型是編程入門學習的基礎類型,從.net誕生2.0開始就出現了泛型,今天我們開始學習泛型的語法和使用。

  什麼是泛型?

  泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.NET框架引入了類型參數(type parameters)的概念。類型參數使得設計類和方法時,不必確定一個或多個具體參數,其的具體參數可延遲到客戶代碼中聲明、實現。這意味著使用泛型的類型參數T,寫一個類MyList<T>,客戶代碼可以這樣調用:MyList<int>, MyList<string>或 MyList<MyClass>。這避免了運行時類型轉換或裝箱操作的代價和風險。

  上面是官方腔調,我說人話:泛型就是為了滿足不同類型,相同代碼的重用!

 

  為什麼要有泛型?

  下麵我們舉例子來講解為什麼會要泛型,以下我列舉了三個例子來講解:

   我們列舉了ShowInt,ShowString,ShowDatatime三個方法,如果我們在程式中每封裝一個方法有不同參數,就要像下麵這樣寫一個函數的話,代碼會變得很累贅,在調用的時候必須傳遞吻合的參數,所以不得不寫出了3個方法,那有沒有好的方法來解決這樣的問題呢?答案是當然有,微軟是很聰明的,傾聽小滿哥慢慢來講。

        /// <summary>
        /// 列印個int值/// </summary>
        /// <param name="iParameter"></param>
        public static void ShowInt(int iParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);
        }

        /// <summary>
        /// 列印個string值/// </summary>
        /// <param name="sParameter"></param>
        public static void ShowString(string sParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);
        }

        /// <summary>
        /// 列印個DateTime值/// </summary>
        /// <param name="oParameter"></param>
        public static void ShowDateTime(DateTime dtParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);
        }
     
     定義一些測試變數

      int iValue = 123;
      string sValue = "456";
      DateTime dtValue = DateTime.Now;
      object oValue = "789";

      普通方式調用演示
      ShowInt(iValue);
      ShowInt(sValue);//這樣不行類型必須吻合
      ShowString(sValue);
      ShowDateTime(dtValue);

  在.net 1.0的時候微軟出現了object這個概念,下麵有一個方法ShowObject,你們就會發現不管參數是int srtring datetime 我們都可以調用ShowObject來操作實現,那為什麼會這樣呢?

  1:Object類型是一切類型的父類。

  2:任何父類出現的地方,都可以用子類來代替。

出現這個基本滿足了開發人員的一些需求,在.net1.0和1.1的時候,這個時候還沒有泛型就用object代替。

        /// <summary>
        /// 列印個object值
        /// 1 object類型是一切類型的父類
        /// 2 任何父類出現的地方,都可以用子類來代替
        /// .Net Framework 1.0 1.1
        /// </summary>
        /// <param name="oParameter"></param>
        public static void ShowObject(object oParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod), oParameter.GetType().Name, oParameter);
        } 
     定義一些測試變數

      int iValue = 123;
      string sValue = "456";
      DateTime dtValue = DateTime.Now;
      object oValue = "789";

     object方式調用演示

      ShowObject(oValue);
      ShowObject(iValue);
      ShowObject(sValue);
      ShowObject(dtValue);



 

  接著吹牛比,勞動人民的智慧還是很屌的,經過之前的經歷在.net2.0的時候,微軟讓主角登場了"泛型",當然同時出現的還有“線程池”這個我們先不講,回到正軌什麼是泛型?你們在開發的時候有沒有用過List<T>這個集合?這個就是泛型,深化下直接舉個慄子吧,下麵我寫一個例子Show<T>(T tParameter)看下泛型的寫法:

  有個毛用?下麵這個方法也可以向上面ShowObject一樣,你們會發現不管參數是int srtring datetime 我們也可以調用Show<T>(T tParameter)來操作實現,替換了ShowObject這個方法的實現,具備了滿足了不同參數類型的方法實現,更適用性,泛型方法聲明的時候,沒有指定類型,而是調用的時候指定,具有延遲聲明和延遲思想,這個思想對我們在開發框架的時候灰常有用,不得不說老外這點還是很幾把厲害(還是我們被洗腦了?也許吧,我相信等中文編程語言出來估計會更屌,中華文化博大精深嘛),現在小伙伴們是不是大概瞭解泛型的基礎作用了?

        /// <summary>
        /// 泛型方法聲明的時候,沒有指定類型,而是調用的時候指定
        /// 延遲聲明:把參數類型的聲明推遲到調用
        /// 延遲思想:推遲一切可以推遲的
        /// 
        /// 2.0泛型不是語法糖,而是由框架升級提供的功能
        /// 泛型方法性能上和普通方法差不多的/// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());
        }

     調用演示

      Show<object>(oValue);
      Show<int>(iValue);
      Show(iValue);//可以去掉,自動推算
      Show<string>(iValue);//必須匹配
      Show<string>(sValue);
      Show<DateTime>(dtValue);

    那問題來了,既然都可以實現為什麼要用這個呢?我們做事凡事都要帶著疑問去看待,有些事別人說好,但真的好不好我們要自己親自試試才知道,我們最關註的的效率問題,下麵是測試代碼:

     public static void Show()
        {
            Console.WriteLine("****************Monitor******************");
            {
                int iValue = 12345;
                long commonSecond = 0;
                long objectSecond = 0;
                long genericSecond = 0;

                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100000000; i++)
                    {
                        ShowInt(iValue);
                    }
                    watch.Stop();
                    commonSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100000000; i++)
                    {
                        ShowObject(iValue);
                    }
                    watch.Stop();
                    objectSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100000000; i++)
                    {
                        Show<int>(iValue);
                    }
                    watch.Stop();
                    genericSecond = watch.ElapsedMilliseconds;
                }
                Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
                    , commonSecond, objectSecond, genericSecond);
            }
        }

  結果如下:commonSecond=508,objectSecond=916,genericSecond=452   你會發現普通方法508ms,object方法916ms,泛型是452ms,其中object最慢,為什麼最慢呢?因為object會經過一個裝箱拆箱的過程,所以性能上會損失一些,但是在我看來這樣上億次這點損耗,算不了什麼,但是可以證明泛型和普通類型速度是差不多的,這一點可以認可泛型還是性能挺好的,這個可以推薦使用泛型的理由之一。

  但泛型僅僅表現在這個層面嗎?遠遠不止,我們用泛型遠遠不是為了提升剛剛那點性能,為什麼要用泛型?答案來了,我們要用泛型就是為了滿足不同類型,相同代碼的重用,下麵我繼續舉慄子:

   泛型的一些用法,泛型只有四種用途,泛型類,泛型介面,泛型委托,泛型方法,如下:

    public class GenericClass<T>
    {
        public T Property { get; set; }
    }

   public interface IGenericInterface<T>
     {

     }

   public delegate void DoNothing<T>();

    調用演示

    List<int> intList = new List<int>();//原生態List類
    List<string> stringList = new List<string>();

    GenericClass<int> iGenericClass = new GenericClass<int>();
    iGenericClass.Property = 1;

    GenericClass<string> sGenericClass = new GenericClass<string>();
    sGenericClass.Property = "1233";

    DoNothing<int> method = new DoNothing<int>(() => { });

    還有一種,別被嚇到:T,S,Xiaomange這些語法,只是占位符別怕,你可以自己定義的,在你調用的時候確定類型就OK了,好了差不多能理解泛型了吧?再說一次泛型就是為了滿足不同類型,相同代碼的重用。

    public class ChildClassGeneric<T, S, XiaoManGe> : GenericClass<T>, IGenericInterface<S>
    {

    }

  接下來我們來聊一聊拓展的一部分,好像泛型很弔的樣子感覺什麼都能用泛型類型代替,但是天下哪有那麼好的事情,雙刃劍的道理都懂,所以出現了泛型的約束這個緊箍咒。

  泛型的約束

  直接來代碼:

  很簡單的一個例子,介面ISports,IWork,People類,Japanese類等簡單繼承了一下,目前準備的一些代碼。

  public interface ISports
    {
        void Pingpang();
    }

    public interface IWork
    {
        void Work();
    }

    public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public void Hi()
        { }
    }
public class Chinese : People, ISports, IWork { public void Tradition() { Console.WriteLine("仁義禮智信,溫良恭儉讓"); } public void SayHi() { Console.WriteLine("吃了麽?"); } public void Pingpang() { Console.WriteLine("打乒乓球..."); } public void Work() { throw new NotImplementedException(); } }
public class Hubei : Chinese { public Hubei(int id) { } public string Changjiang { get; set; } public void Majiang() { Console.WriteLine("打麻將啦。。"); } }
public class Japanese : ISports { public int Id { get; set; } public string Name { get; set; } public void Pingpang() { Console.WriteLine("打乒乓球..."); } public void Hi() { } }

  再來個調用類Constraint

 public class Constraint
    {
        /// <summary>
        /// 代碼編譯沒問題,執行的時候才報錯
        /// 代碼安全問題
        /// </summary>
        /// <param name="oParameter"></param>
        public static void ShowObject(object oParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod), oParameter.GetType().Name, oParameter);

            People people = (People)oParameter;

            Console.WriteLine(people.Id);//這裡就不行了 代碼安全問題,調用不到,但編譯不會報錯。
            Console.WriteLine(people.Name);
        }

        /// <summary>
        /// 基類約束:
        /// 1 帶來權力,可以使用基類裡面的屬性和方法
        /// 2 帶來義務,類型參數必須是基類或者其子類
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter)
            where T : People, ISports, new()//都是and 關係
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());

            Console.WriteLine(tParameter.Id);
            Console.WriteLine(tParameter.Name);
            tParameter.Hi();
            tParameter.Pingpang();
            T t = new T();
        }

        public static void ShowPeople(People tParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());

            Console.WriteLine(tParameter.Id);
            Console.WriteLine(tParameter.Name);
            tParameter.Hi();
            //tParameter.Pingpang();
        }

        public static T DoNothing<T>(T tParameter)
            //where T : ISports//介面約束
            //where T : class//引用類型約束
            //where T : struct//值類型約束
            where T : new()//無參數構造函數約束
        {
            //tParameter.Pingpang();
            //return null;
            T t = new T();
            return default(T);//會根據T的類型,去產生一個預設值
        }
    }

  有興趣的可以測試下,用ShowObject的方法和泛型Show<T>(T tParameter)調用來看差異,如果不加入約束,想調用參數的屬性和方法,代碼安全問題是調用不了的,會報錯,但是加入基類約束之後是可以調用到的,所以泛型約束帶來了權利,可以使用基類的屬性和方法,但也帶來義務,參數只能是基類和子類,又想馬兒跑,又想馬兒不吃草的事情是沒有的,權利和義務是相對的,在享受權利的同時也會帶來義務。

  其次,約束可以多重約束,然後即可作為參數約束也可以作為返回值約束,例如default(T)會根據泛型類型返回一個預設值,如果是無參數構造約束就可以類似這樣寫返回值T t=new T()。

  總之,我覺得泛型約束為了更靈活的滿足不同條件的需求而產生的,就是我們在寫一些固定的需求,約束疊加來完成我們的功能,同時不讓泛型肆無忌憚。

        泛型約束範圍如下:

約束

描述

where T: struct

類型參數必須為值類型。

where T : class

類型參數必須為引用類型。

where T : new()

類型參數必須有一個公有、無參的構造函數。當於其它約束聯合使用時,new()約束必須放在最後。

where T : <base class name>

類型參數必須是指定的基類型或是派生自指定的基類型。

where T : <interface name>

類型參數必須是指定的介面或是指定介面的實現。可以指定多個介面約束。介面約束也可以是泛型的。

  好了泛型的約束的先說到這,繼續套底子,瞭解到的都倒出來。

 

  逆變和協變

  逆變和協變不知道有沒有小伙伴熟悉的,我開始是不知道這個的,第一次看到也是一臉懵逼,到現在也有點迷糊,能不能講清楚看造化了,哈哈

   繼續舉慄子:

//協變
public
interface IEnumerable<out T> : IEnumerable
//逆變
public delegate void Action<in T>(T obj);

  這段代碼是不是很熟悉?裡面有個Out T 還有 in T,這裡的Out 不是我們熟悉的參數返回Out,ref的作用,是協變專用的,逆變和協變指出現在介面或者委托泛型前面,

  In只能作為參數傳入,Out只能作為參數傳出。

  下麵來個代碼  

  

    public class Bird
    {
        public int Id { get; set; }
    }
    public class Sparrow : Bird
    {
        public string Name { get; set; }
    }
調用實例 IEnumerable
<int> intList = new List<int>(); Action<int> iAction = null; Bird bird1 = new Bird(); Bird bird2 = new Sparrow();//左邊是父類 右邊是子類 Sparrow sparrow1 = new Sparrow(); //Sparrow sparrow2 = new Bird();//不是所有的鳥,都是麻雀 List<Bird> birdList1 = new List<Bird>();//一群鳥 是一群鳥 //List<Bird> birdList2 = new List<Sparrow>();//一群麻雀難道不是一群鳥 ? 不是的,沒有父子關係 List<Bird> birdList3 = new List<Sparrow>().Select(c => (Bird)c).ToList();//這裡使用彆扭了,明明知道但就是不能這樣寫

  以上代碼發現問題了嗎?很明顯出現了一些不和諧的地方,我們換個方式如下: 

      
      
   /// <summary>
    /// 逆變:只能修飾傳入參數
    /// </summary>
    /// <typeparam name="T"></typeparam>
    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)
        {
        }
    }

    /// <summary>
    /// out 協變 只能是返回結果
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICustomerListOut<out T>
    {
        T Get();

        //void Show(T t);//不能做參數
    }

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

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

        //}
    }

 



       {//協變:介面泛型參數加了個out,就是為瞭解決剛纔的不和諧 IEnumerable<Bird> birdList1 = new List<Bird>(); IEnumerable<Bird> birdList2 = new List<Sparrow>(); //Func<Bird> func = new Func<Sparrow>(() => null); ICustomerListOut<Bird> customerList1 = new CustomerListOut<Bird>(); ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>(); } {//逆變 ICustomerListIn<Sparrow> customerList2 = new CustomerListIn<Sparrow>(); ICustomerListIn<Sparrow> customerList1 = new CustomerListIn<Bird>(); //customerList1.Show() ICustomerListIn<Bird> birdList1 = new CustomerListIn<Bird>(); birdList1.Show(new Sparrow()); birdList1.Show(new Bird()); Action<Sparrow> act = new Action<Bird>((Bird i) => { }); }

  這樣可以了,協變IEnumerable加入協變Out 左邊是個父類,右邊可以是子類,逆變In 左邊是個字類,右邊也可以是父類,下麵這段就更暈了,稍微看下吧。

       {
                IMyList<Sparrow, Bird> myList1 = new MyList<Sparrow, Bird>();
                IMyList<Sparrow, Bird> myList2 = new MyList<Sparrow, Sparrow>();//協變
                IMyList<Sparrow, Bird> myList3 = new MyList<Bird, Bird>();//逆變
                IMyList<Sparrow, Bird> myList4 = new MyList<Bird, Sparrow>();//協變+逆變
            }

  總結下原理:泛型在JIT編譯時指定具體類型,同一個泛型類,不同的類型參數,其實會變成不用的類型。

  我走的很慢,但從不後退!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [TOC] 近期廣泛閱讀券商關於 巨集觀高頻數據 的研報,發現了兩點不足: 就研究手段而言,比較粗放,普遍停留在僅僅比較數據相關係數的層面; 就理論高度而言,很少探討數據背後的因果關聯。 不過有些理念先進的券商團隊已經開始從 產業鏈傳導 的角度試圖細緻的描述數據間的關聯,這正好契合了下麵這篇文章的核心 ...
  • 「HW面試題」 【題目】 給定一個整數數組,如何快速地求出該數組中第k小的數。假如數組為[4,0,1,0,2,3],那麼第三小的元素是1 【題目分析】 這道題涉及整數列表排序問題,直接使用sort方法按照ASCII碼排序即可 【解答】 1 #!/Users/minutesheep/.pyenv/sh ...
  • 第5章 字元串及正則表達式 5.1 字元串常用操作 在Python開發過程中,為了實現某項功能,經常需要對某些字元串進行特殊處理,如拼接字元串、截取字元串、格式化字元串等。下麵將對Python中常用的字元串操作方法進行介紹。 5.1.1 拼接字元串 使用“+” 運算符可完成對多個字元串的拼接,“+” ...
  • Spark RPC 框架的運行時序是怎樣的呢?讓我們深入到它的源碼裡面去看看~~ ...
  • 實例中,可以通過person中的不同類型的對象來實現不同的方法。 ...
  • 企業微信的支付自從企業號變化為企業微信後,增加了一些支付介面以及對很多介面進行了調整,企業微信的支付處理也是變化了不少,往往有時候碰到不少坑,一步一個腳印趟過來的;企業微信支付是需要結合微信商戶後臺進行處理,有時候也需要設置好商戶平臺的相關處理,才能進行發送紅包、支付到個人等等支付處理。本篇隨筆介紹... ...
  • 錯誤實例一:父類的訪問性低於子類 錯誤實例二:方法的訪問修飾符需要和參數的類型的訪問修飾符一致 類似的問題都是可訪問性不一致造成的,修改訪問修飾符即可。 ...
  • 一、.NET MVC 學習筆記(一)——新建MVC工程 接觸MVC有段時間了,一直想找機會整理一下,可是限於文筆太差,所以一直遲遲羞於下手,想到最近做過的MVC項目也有一些了,花點時間整理一下方便以後工作。 開發環境: VS2015 1. 打開VS2015 2. 點擊【新建項目...】,選擇【ASP ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...