• C#泛型学习笔记


       本笔记摘抄自:https://www.cnblogs.com/dotnet261010/p/9034594.html,记录一下学习过程以备后续查用。

        一、什么是泛型

        泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。泛型类就类似于一个模板,可以在需要时为这个模板传入任何我们需要的类型。

        二、为什么使用泛型

        下面代码演示输出几种类型的相关信息:

        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与其它类型的转换,涉及到装箱和拆箱的过程,这个是会损耗程序的性能的。

        三、泛型类型参数

        在泛型类型或方法的定义中,泛型类型参数可认为是特定类型的占位符。

        下面代码演示使用泛型的方式输出几种类型的相关信息:

        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

        运行结果如下:

        1、为什么泛型可以解决上面的问题呢?

        泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到调用的时候才给它指定。 

        2、泛型究竟是如何工作的呢?

        程序执行原理:控制台程序最终会编译成一个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

        运行结果如下:

        3、泛型性能问题

        下面代码演示泛型性能测试:

        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

        运行结果如下:

        从结果可以看出,泛型的性能是最高的。

        四、泛型类

        下面代码演示泛型类:

        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

        运行结果如下:

        五、泛型接口

        注:泛型在声明的时候可以不指定具体的类型,继承的时候也可以不指定具体类型,但是在使用的时候必须指定具体类型。

        下面代码演示泛型接口:

        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

        运行结果如下:

        六、泛型委托

        下面代码演示泛型委托:

        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

        运行结果如下:

        七、泛型约束

        泛型约束,实际上就是约束的类型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

        运行结果如下:

        九、泛型缓存

        类中的静态类型无论实例化多少次,在内存中只会有一个,静态构造函数只会执行一次。在泛型类中,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次,另外每次静态属性的值都是一样的。利用泛型的这一特性,可以实现缓存。

        注:只能为不同的类型缓存一次;泛型缓存比字典缓存效率高;泛型缓存不能主动释放。

  • 相关阅读:
    NC_6_TREE_MAX_PATH
    NC_12_reConstructBinaryTree
    NC_15_levelOrder
    NC_7_MAXPROFIT
    NC_9_HAS_PATH_SUM
    NC_8_BINARYTREE_SUMPATH
    NC_13_MAX_DEPTH
    IDEA的基本使用:让你的IDEA有飞一般的感觉
    Java获取当前系统事件System.currentTimeMillis()方法 ,获取当前时间戳10位 1665291145 转为时间字符串 yyyMMdd
    JAVA中计算两个日期时间的差值竟然也有这么多门道
  • 原文地址:https://www.cnblogs.com/atomy/p/12060753.html
Copyright © 2020-2023  润新知