第一課《.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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...