C#泛型學習筆記

   本筆記摘抄自:http://www.javashuo.com/article/p-tluiwrcw-do.html,記錄一下學習過程以備後續查用。html

    1、什麼是泛型數組

    泛型是C#2.0推出的新語法,不是語法糖,而是2.0由框架升級提供的功能。泛型類就相似於一個模板,能夠在須要時爲這個模板傳入任何咱們須要的類型。緩存

    2、爲何使用泛型框架

    下面代碼演示輸出幾種類型的相關信息:ide

    class Program
    {
        /// <summary>
        /// 打印幫助類
        /// </summary>
        public class ShowHelper
        {
            /// <summary>
            /// ShowInt
            /// </summary>
            /// <param name="intParam"></param>
            public static void ShowInt(int intParam)
            {
                Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={intParam.GetType().Name},Parameter={intParam}");
            }

            /// <summary>
            /// ShowString
            /// </summary>
            /// <param name="strParam"></param>
            public static void ShowString(string strParam)
            {
                Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={strParam.GetType().Name},Parameter={strParam}");
            }

            /// <summary>
            /// ShowDateTime
            /// </summary>
            /// <param name="dtParam"></param>
            public static void ShowDateTime(DateTime dtParam)
            {
                Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={dtParam.GetType().Name},Parameter={dtParam}");
            }
        }

        static void Main(string[] args)
        {
            #region 非泛型打印方式一
            ShowHelper.ShowInt(123);
            ShowHelper.ShowString("Hello World.");
            ShowHelper.ShowDateTime(DateTime.Now);
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:函數

    上面3個方法很類似,除了參數類型不一樣外,實現的功能是同樣的,能夠稍做優化。性能

    下面代碼演示使用繼承的方式輸出幾種類型的相關信息:學習

    class Program
    {
        /// <summary>
        /// 打印幫助類
        /// </summary>
        public class ShowHelper
        {
            /// <summary>
            /// ShowType
            /// </summary>
            /// <param name="obj"></param>
            public static void ShowType(object obj)
            {
                Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={obj.GetType().Name},Parameter={obj}");
            }
        }

        static void Main(string[] args)
        {
            #region 非泛型打印方式二
            ShowHelper.ShowType(123);
            ShowHelper.ShowType("Hello World.");
            ShowHelper.ShowType(DateTime.Now);
            Console.Read();
            #endregion
        }
    }
View Code

    功能實現沒有問題,只是object與其它類型的轉換,涉及到裝箱和拆箱的過程,這個是會損耗程序的性能的。測試

    3、泛型類型參數優化

    在泛型類型或方法的定義中,泛型類型參數可認爲是特定類型的佔位符。

    下面代碼演示使用泛型的方式輸出幾種類型的相關信息:

    class Program
    {
        /// <summary>
        /// 打印幫助類
        /// </summary>
        public class ShowHelper
        {
            /// <summary>
            /// Show
            /// </summary>
            /// <param name="obj"></param>
            public static void Show<T>(T tParam)
            {
                Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={tParam.GetType().Name},Parameter={tParam}");
            }
        }

        static void Main(string[] args)
        {
            #region 泛型打印方式
            ShowHelper.Show(123);
            ShowHelper.Show("Hello World.");
            ShowHelper.Show(DateTime.Now);
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:

    一、爲何泛型能夠解決上面的問題呢?

    泛型是延遲聲明的:即定義的時候沒有指定具體的參數類型,把參數類型的聲明推遲到調用的時候纔給它指定。 

    二、泛型到底是如何工做的呢?

    程序執行原理:控制檯程序最終會編譯成一個exe程序。當exe被點擊的時候,會通過JIT(即時編譯器)的編譯,最終生成二進制代碼才能被計算機執行。

    泛型工做原理:泛型加入到語法之後,VS自帶的編譯器作了升級,升級以後編譯時若遇到泛型,會作特殊的處理:生成佔位符。而後通過JIT編譯的時候,

會把上面編譯生成的佔位符替換成具體的數據類型。

    下面代碼演示泛型佔位符:

    class Program
    {
        static void Main(string[] args)
        {
            #region 泛型佔位符
            Console.WriteLine(typeof(List<>));
            Console.WriteLine(typeof(Dictionary<,>));
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:

    三、泛型性能問題

    下面代碼演示泛型性能測試:

    class Program
    {
        static void Main(string[] args)
        {
            #region 泛型性能測試
            long commonTime = 0;
            long objectTime = 0;
            long genericTime = 0;
            Stopwatch watch = new Stopwatch();
            watch.Start();
            for (int i = 0; i < 10000; i++)
            {
                ShowHelper.ShowInt(123);
            }
            watch.Stop();
            commonTime = watch.ElapsedMilliseconds;

            watch.Reset();
            watch.Start();
            for (int i = 0; i < 10000; i++)
            {
                ShowHelper.ShowType(123);
            }
            watch.Stop();
            objectTime = watch.ElapsedMilliseconds;

            watch.Reset();
            watch.Start();
            for (int i = 0; i < 10000; i++)
            {
                ShowHelper.Show(123);
            }
            watch.Stop();
            genericTime = watch.ElapsedMilliseconds;

            Console.Clear();
            Console.WriteLine($"Common time={commonTime}ms");
            Console.WriteLine($"Object time={objectTime}ms");
            Console.WriteLine($"Generic time={genericTime}ms");
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:

    從結果能夠看出,泛型的性能是最高的。

    4、泛型類

    下面代碼演示泛型類:

    class Program
    {
        /// <summary>
        /// 泛型類
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class GenericClass<T>
        {
            public T varT;
        }

        static void Main(string[] args)
        {
            #region 泛型類
            //T是int類型
            GenericClass<int> genericInt = new GenericClass<int>
            {
                varT = 123
            };
            Console.WriteLine($"The value of T={genericInt.varT}");
            //T是string類型
            GenericClass<string> genericString = new GenericClass<string>
            {
                varT = "123"
            };
            Console.WriteLine($"The value of T={genericString.varT}");
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:

    5、泛型接口

    注:泛型在聲明的時候能夠不指定具體的類型,繼承的時候也能夠不指定具體類型,可是在使用的時候必須指定具體類型。

    下面代碼演示泛型接口:

    class Program
    {
        /// <summary>
        /// 泛型接口
        /// </summary>
        public interface IGenericInterface<T>
        {
            T GetT(T t);
        }

        /// <summary>
        /// 泛型接口實現類
        /// </summary>
        /// <param name="args"></param>
        public class GenericGet<T> : IGenericInterface<T>
        {
            T varT;
            public T GetT(T t)
            {
                varT = t;
                return varT;
            }
        }

        static void Main(string[] args)
        {
            #region 泛型接口
            IGenericInterface<int> genericInterface = new GenericGet<int>();
            var result = genericInterface.GetT(123);
            Console.WriteLine($"Result={result}");
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:

    6、泛型委託

    下面代碼演示泛型委託:

    class Program
    {
        /// <summary>
        /// 泛型委託
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public delegate void SayHi<T>(T t);

        static void Main(string[] args)
        {
            #region 泛型委託
            SayHi<string> sayHi = SayHello;
            sayHi("Hello World");
            Console.Read();
            #endregion
        }

        /// <summary>
        /// SayHello
        /// </summary>
        /// <param name="greeting"></param>
        public static void SayHello(string greeting)
        {
            Console.WriteLine($"{greeting}");
        }
    }
View Code

    運行結果以下:

    7、泛型約束

    泛型約束,實際上就是約束的類型T,使T必須遵循必定的規則。好比T必須繼承自某個類或者T必須實現某個接口等等。

    怎樣給泛型指定約束?其實也很簡單,只須要where關鍵字,加上約束的條件。

    泛型約束總共有五種:

約束 s說明
T:結構 類型參數必須是值類型
T:類 類型參數必須是引用類型;這一點也適用於任何類、接口、委託或數組類型。
T:new() 類型參數必須具備無參數的公共構造函數。 當與其餘約束一塊兒使用時,new() 約束必須最後指定。
T:<基類名> 類型參數必須是指定的基類或派生自指定的基類。
T:<接口名稱> 類型參數必須是指定的接口或實現指定的接口。 能夠指定多個接口約束。 約束接口也能夠是泛型的。

    7.1基類約束

    下面代碼演示基類約束:

        /// <summary>
        /// 運動類接口
        /// </summary>
        public interface ISports
        {
            void Pingpong();
        }

        /// <summary>
        /// 人類基類
        /// </summary>
        public class People
        {
            public string Name { get; set; }

            public virtual void Greeting()
            {
                Console.WriteLine("Hello World.");
            }
        }

        /// <summary>
        /// 中國人
        /// </summary>
        public class Chinese : People, ISports
        {
            public void FineTradition()
            {
                Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。");
            }
            public override void Greeting()
            {
                Console.WriteLine("吃飯了沒?");
            }

            public void Pingpong()
            {
                Console.WriteLine("乒乓球是中國的國球。");
            }
        }

        static void Main(string[] args)
        {
            #region 泛型約束:基類約束
            Chinese chinese = new Chinese()
            {
                Name = "中國人"
            };
            ShowPeople(chinese);
            Console.Read();
            #endregion
        }

        /// <summary>
        /// 基類約束
        /// </summary>
        /// <param name="obj"></param>
        public static void ShowPeople<T>(T tParam) where T:People
        {
            Console.WriteLine($"{((People)tParam).Name}");
        }
    }
View Code

    運行結果以下:

    注:基類約束時,基類不能是密封類,即不能是sealed類。sealed類表示該類不能被繼承,在這裏用做約束就無任何意義了,由於sealed類沒有子類。

    7.2接口約束

    下面代碼演示接口約束:

    class Program
    {
        /// <summary>
        /// 運動類接口
        /// </summary>
        public interface ISports
        {
            void Pingpong();
        }

        /// <summary>
        /// 人類基類
        /// </summary>
        public class People
        {
            public string Name { get; set; }

            public virtual void Greeting()
            {
                Console.WriteLine("Hello World.");
            }
        }

        /// <summary>
        /// 中國人
        /// </summary>
        public class Chinese : People, ISports
        {
            public void FineTradition()
            {
                Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。");
            }
            public override void Greeting()
            {
                Console.WriteLine("吃飯了沒?");
            }

            public void Pingpong()
            {
                Console.WriteLine("乒乓球是中國的國球。");
            }
        }

        static void Main(string[] args)
        {
            #region 泛型約束:接口約束
            Chinese chinese = new Chinese()
            {
                Name = "中國人"
            };
            GetSportsByInterface(chinese);
            Console.Read();
            #endregion
        }

        /// <summary>
        /// 接口約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T GetSportsByInterface<T>(T t) where T : ISports
        {
            t.Pingpong();
            return t;
        }
    }
View Code

    運行結果以下:

    7.3引用類型約束 class

    引用類型約束保證T必定是引用類型的。

    下面代碼演示引用類型約束:

    class Program
    {
        /// <summary>
        /// 運動類接口
        /// </summary>
        public interface ISports
        {
            void Pingpong();
        }

        /// <summary>
        /// 人類基類
        /// </summary>
        public class People
        {
            public string Name { get; set; }

            public virtual void Greeting()
            {
                Console.WriteLine("Hello World.");
            }
        }

        /// <summary>
        /// 中國人
        /// </summary>
        public class Chinese : People, ISports
        {
            public void FineTradition()
            {
                Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。");
            }
            public override void Greeting()
            {
                Console.WriteLine("吃飯了沒?");
            }

            public void Pingpong()
            {
                Console.WriteLine("乒乓球是中國的國球。");
            }
        }

        static void Main(string[] args)
        {
            #region 泛型約束:引用類型約束
            Chinese chinese = new Chinese()
            {
                Name = "中國人"
            };
            GetSportsByClass(chinese);
            Console.Read();
            #endregion
        }

        /// <summary>
        /// 引用類型約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T GetSportsByClass<T>(T t) where T : class
        {
            if (t is ISports)
            {
                (t as ISports).Pingpong();
            }
            return t;
        }
    }
View Code

    運行結果以下:

    7.4值類型約束 struct

    值類型約束保證T必定是值類型的。

    下面代碼演示值類型約束:

    class Program
    {
        /// <summary>
        /// 績效工資
        /// </summary>
        public struct Achievement
        {
            public double MeritPay { get; set; }
            public string Level { get; set; }
            public double ReallyPay()
            {
                switch (Level)
                {
                    case "A":
                        MeritPay = MeritPay * 1.0;
                        break;
                    case "B":
                        MeritPay = MeritPay * 0.8;
                        break;
                    case "C":
                        MeritPay = MeritPay * 0.6;
                        break;
                    case "D":
                        MeritPay = 0;
                        break;
                    default:
                        MeritPay = 0;
                        break;
                };
                return MeritPay;
            }
        }

        static void Main(string[] args)
        {
            #region 泛型約束:值類型約束
            Achievement achievement = new Achievement
            {
                MeritPay = 500,
                Level = "B"
            };
            var result = GetReallyPay(achievement).ReallyPay();
            Console.WriteLine($"ReallyPay={result}");
            Console.Read();
            #endregion
        }

        /// <summary>
        /// 值類型約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T GetReallyPay<T>(T t) where T : struct
        {
            return t;
        }
    }
View Code

    運行結果以下:

    7.5無參數構造函數約束 new() 

    下面代碼演示無參數構造函數約束:

    class Program
    {
        /// <summary>
        /// 運動類接口
        /// </summary>
        public interface ISports
        {
            void Pingpong();
        }

        /// <summary>
        /// 人類基類
        /// </summary>
        public class People
        {
            public string Name { get; set; }

            public virtual void Greeting()
            {
                Console.WriteLine("Hello World.");
            }
        }

        /// <summary>
        /// 中國人
        /// </summary>
        public class Chinese : People, ISports
        {
            public void FineTradition()
            {
                Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。");
            }
            public override void Greeting()
            {
                Console.WriteLine("吃飯了沒?");
            }

            public void Pingpong()
            {
                Console.WriteLine("乒乓球是中國的國球。");
            }
        }

        /// <summary>
        /// 廣東人
        /// </summary>
        public class Guangdong : Chinese
        {
            public Guangdong() { }
            public string Dialect { get; set; }
            public void Mahjong()
            {
                Console.WriteLine("這麻將上癮的時候,一我的也說是三缺一呀。");
            }
        }

        static void Main(string[] args)
        {
            #region 泛型約束:無參數構造函數約束
            Guangdong guangdong = new Guangdong()
            {
                Name = "廣東人"
            };
            GetMahjong(guangdong);
            Console.Read();
            #endregion
        }

        /// <summary>
        /// 無參數構造函數約束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static T GetMahjong<T>(T t) where T : People, ISports, new()
        {
            if (t is Guangdong)
            {
                (t as Guangdong).Mahjong();
            }
            return t;
        }
    }
View Code

    運行結果以下:

    從上面能夠看出,泛型約束能夠有多個,可是有多個泛型約束時,new()約束要放到最後。

    八:泛型的協變和逆變

    協變和逆變是在.NET 4.0的時候出現的,只能放在接口或者委託的泛型參數前面,out協變covariant,用來修飾返回值;in:逆變contravariant,用來修飾

傳入參數。

    下面代碼演示父類與子類的聲明方式:

    class Program
    {
        /// <summary>
        /// 動物基類
        /// </summary>
        public class Animal
        {
            public int Breed { get; set; }
        }

        /// <summary>
        /// 貓類
        /// </summary>
        public class Cat : Animal
        {
            public string Name { get; set; }
        }

        static void Main(string[] args)
        {
            #region 泛型的協變和逆變
            //直接聲明Animal類
            Animal animal = new Animal();
            //直接聲明Cat類
            Cat cat = new Cat();
            //聲明子類對象指向父類
            Animal animal2 = new Cat();
            //聲明Animal類的集合
            List<Animal> listAnimal = new List<Animal>();
            //聲明Cat類的集合
            List<Cat> listCat = new List<Cat>();
            #endregion
        }
    }
View Code

    以上代碼是能夠正常運行的。假如使用下面的聲明方式,是否正確呢?

List<Animal> list = new List<Cat>();

    答案是錯誤的,由於List<Animal>和List<Cat>之間沒有父子關係。

    解決方法是使用協變的方式:

IEnumerable<Animal> List1 = new List<Animal>();
IEnumerable<Animal> List2 = new List<Cat>();

    按F12查看IEnumerable定義:

    能夠看到,在泛型接口的T前面有一個out關鍵字修飾,並且T只能是返回值類型,不能做爲參數類型,這就是協變。使用協變之後,左邊聲明的是基類,

右邊的聲明能夠是基類或者基類的子類。

    協變除了能夠用在接口上面外,還能夠用在委託上面:

Func<Animal> func = new Func<Cat>(() => null);

    除了使用.NET框架定義好協變之外,咱們也能夠自定義協變:

//使用自定義協變
ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();

    再來看看逆變

    在泛型接口的T前面有一個In關鍵字修飾,並且T只能方法參數,不能做爲返回值類型,這就是逆變。

/// <summary>
/// 逆變 只能是方法參數
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListIn<in T>
{
     void Show(T t);
}

public class CustomerListIn<T> : ICustomerListIn<T>
{
     public void Show(T t)
     {
     }
}
View Code

    使用自定義逆變:

//使用自定義逆變
ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>();
ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();

    協變和逆變也能夠同時使用。

    下面代碼演示自定義協變與逆變:

    class Program
    {
        /// <summary>
        /// 動物基類
        /// </summary>
        public class Animal
        {
            public int Breed { get; set; }
        }

        /// <summary>
        /// 貓類
        /// </summary>
        public class Cat : Animal
        {
            public string Name { get; set; }
        }

        #region 泛型的自定義協變和逆變
        /// <summary>
        /// inT-逆變 outT-協變
        /// </summary>
        /// <typeparam name="inT"></typeparam>
        /// <typeparam name="outT"></typeparam>
        public interface IMyList<in inT, out outT>
        {
            void Show(inT t);
            outT Get();
            outT Do(inT t);
        }

        public class MyList<T1, T2> : IMyList<T1, T2>
        {

            public void Show(T1 t)
            {
                Console.WriteLine(t.GetType().Name);
            }

            public T2 Get()
            {
                Console.WriteLine(typeof(T2).Name);
                return default(T2);
            }

            public T2 Do(T1 t)
            {
                Console.WriteLine(t.GetType().Name);
                Console.WriteLine(typeof(T2).Name);
                return default(T2);
            }
        }
        #endregion

        static void Main(string[] args)
        {
            #region 泛型的自定義協變與逆變
            IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>();
            IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();          //協變
            IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();    //逆變
            IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();       //逆變+協變
            myList1.Get();
            myList2.Get();
            myList3.Get();
            myList4.Get();
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:

    9、泛型緩存

    類中的靜態類型不管實例化多少次,在內存中只會有一個,靜態構造函數只會執行一次。在泛型類中,T類型不一樣,每一個不一樣的T類型,都會產生一個不一樣

的副本,因此會產生不一樣的靜態屬性、不一樣的靜態構造函數。

    下面代碼演示泛型緩存:

    class Program
    {
        /// <summary>
        /// 泛型緩存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class GenericCache<T>
        {
            private static readonly string TypeTime = "";
            static GenericCache()
            {
                Console.WriteLine("這個是泛型緩存的靜態構造函數:");
                TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
            }
            public static string GetCache()
            {
                return TypeTime;
            }
        }

        /// <summary>
        /// 泛型緩存測試類
        /// </summary>
        public class GenericCacheTest
        {
            public static void Show()
            {
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine(GenericCache<int>.GetCache());
                    Thread.Sleep(10);
                    Console.WriteLine(GenericCache<long>.GetCache());
                    Thread.Sleep(10);
                    Console.WriteLine(GenericCache<DateTime>.GetCache());
                    Thread.Sleep(10);
                    Console.WriteLine(GenericCache<string>.GetCache());
                    Thread.Sleep(10);
                    Console.WriteLine(GenericCache<GenericCacheTest>.GetCache());
                    Thread.Sleep(10);
                }
            }
        }

        static void Main(string[] args)
        {
            #region 泛型緩存
            GenericCacheTest.Show();
            Console.Read();
            #endregion
        }
    }
View Code

    運行結果以下:

    從上面的截圖中能夠看出,泛型會爲不一樣的類型都建立一個副本,所以靜態構造函數會執行5次,另外每次靜態屬性的值都是同樣的。利用泛型的這一特性,能夠實現緩存。

    注:只能爲不一樣的類型緩存一次;泛型緩存比字典緩存效率高;泛型緩存不能主動釋放。

相關文章
相關標籤/搜索