一、C#泛型学习笔记

泛型知识点对应的学习代码

1、什么是泛型?

宽泛的,不确定的类型,如:

List<string> list1 = new List<string>();
List<int> list2 = new List<int>();

2、为什么要用泛型?意义是什么?

在操作一致,但是数据类型不一致时,泛型的意义就显现出来了

3、泛型的声明与使用

泛型就是在声明的时候带上一个,这个T就是泛型参数,在调用的时候才会确定参数类型

4、泛型的延迟思想

泛型实际上就是延迟声明的思想,在开发中,延迟一切可以延迟的处理,能晚点做就晚点做
类型参数在声明的时候定义一个占位符,在调用的时候才会确定类型

5.采用object类型也可以做到不同类型的替换

原因:
① 性能损耗:需要“装箱”、“拆箱”操作
基本类型变量都存在于栈内存中,引用类型变量存在于堆内存中,object类型属于引用类型
所谓“装箱”是指,将基本类型变量从栈内存复制到堆内存变成object类型变量
所谓“拆箱”是指,将object类型变量从堆内存复制到栈内存变成基本类型变量
② 存在类型安全问题

6.泛型的应用

在声明的时候用一个类型参数T代替不确定的数据类型,这就是反省
在声明的时候数据类型不确定,只有再调用的时候才会确定具体的数据类型
泛型在编译后,会根据泛型参数个数生成占位符
最终会根据调用生成不同的
泛型在.Net Framework2.0版本就已经出来了,需要以下升级的支持
① 编译器升级,能够支持类型参数用占位符来表达
② 公共语言运行库(CLR)升级才能支持占位符,运行时确定实际类型

7.泛型的应用对象:类、方法、接口、委托

泛型类:一个类满足不同类的需求
泛型方法:一个方法满足不同类型的需求
泛型接口:一个接口满足不同接口的需求
泛型委托:一个委托满足不同委托的需求

/// <summary>
        /// 打印int类型数据
        /// </summary>
        /// <param name="iParam"></param>
        public static void ShowInt(int iParam)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}", typeof(BasicKnowledge).Name, iParam, iParam.GetType());
        }

        /// <summary>
        /// 打印string类型数据
        /// </summary>
        /// <param name="sParam"></param>
        public static void ShowString(string sParam)
        {
            Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={sParam},type={sParam.GetType()}");
        }

        /// <summary>
        /// 打印datetime类型数据
        /// </summary>
        /// <param name="dParam"></param>
        public static void ShowDateTime(DateTime dParam)
        {
            Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={dParam},type={dParam.GetType()}");
        }

        /// <summary>
        /// 采用里氏替换原则打印各种类型数据
        /// </summary>
        /// <param name="oParam"></param>
        public static void ShowObject(object oParam)
        {
            Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={oParam},type={oParam.GetType()}");
        }

        /// <summary>
        /// 应用泛型打印各种类型数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tParam"></param>
        public static void ShowGeneric<T>(T tParam)
        {
            Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={tParam},type={tParam.GetType()}");
        }

        /// <summary>
        /// 对比普通方法、里氏替换、泛型方法的性能
        /// 
        /// 经过实验,里氏替换方法性能最低,耗时大概是泛型方法的三倍左右,泛型方法与普通方法相差不多,泛型方法耗时稍微少一点
        /// </summary>
        /// <param name="iParam"></param>
        public static void ShowCompare(int iParam)
        {
            long commonTime = 0;
            long objectTime = 0;
            long genericTime = 0;

            Stopwatch commonWatch = new Stopwatch();
            Stopwatch objectWatch = new Stopwatch();
            Stopwatch genericWatch = new Stopwatch();

            commonWatch.Start();
            for(int i = 0; i < 20_000_000; i++)
            {
                ComInt(iParam);
            }
            commonWatch.Stop();
            commonTime = commonWatch.ElapsedMilliseconds;

            objectWatch.Start();
            for(int i = 0; i < 20_000_000; i++)
            {
                ComObject(iParam);
            }
            objectWatch.Stop();
            objectTime = objectWatch.ElapsedMilliseconds;

            genericWatch.Start();
            for (int i = 0; i < 20_000_000; i++)
            {
                ComGeneric<int>(iParam);
            }
            genericWatch.Stop();
            genericTime = genericWatch.ElapsedMilliseconds;

            Console.WriteLine($"普通方法耗时:{commonTime},里氏替换方法耗时:{objectTime},泛型方法耗时:{genericTime}");
        }

        #region
        private static void ComInt(int iParam)
        {

        }

        private static void ComObject(object oParam)
        {

        }

        private static void ComGeneric<T>(T tParam)
        {

        }
        #endregion

执行结果:
基础部分代码运行结果
经运行结果可知,里氏替换方法耗时最多,约为普通方法和泛型方法的三倍左右,泛型方法比普通方法耗时还要少一点

8.泛型缓存

缓存实质上是一个暂存区
① 静态字典缓存
② 泛型缓存

	//静态字典缓存
    public class DictionaryCache
    {
        private static Dictionary<Type, string> dic_Cache = null;

        static DictionaryCache()
        {
            dic_Cache = new Dictionary<Type, string>();
            Console.WriteLine("This is DictionaryCache 静态代码块");
        }

        public static string GetCache<T>()
        {
            Type type = typeof(T);

            if (!dic_Cache.ContainsKey(type))
            {
                dic_Cache.Add(type, $"{type.FullName}_{DateTime.Now:yyyy-MM-dd hh:mm:ss.fff}");
                Console.WriteLine($"添加到静态字典缓存中的类型是:{dic_Cache[type]}");
            }

            return dic_Cache[type];
        }
    }
	/// <summary>
    /// 泛型缓存
    /// 泛型缓存实质上是一个泛型类,根据调用不同的类型生成不同的副本
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GenericCache<T>
    {
        private static string generic_Cache = "";

        /// <summary>
        /// 静态代码块执行且只执行一次
        /// 但是这个静态代码块却执行了多次
        /// 因为只要添加一个新的类型,就相当于生成了一个新的类的副本
        /// 就会开辟出一个新的内存来存放这个“新的类”
        /// </summary>
        static GenericCache()
        {
            generic_Cache = $"{typeof(T).FullName}_{DateTime.Now:yyyy-MM-dd hh:mm:ss.fff}";
            Console.WriteLine("This is GenericCache 静态代码块");
        }

        public static string GetCache()
        {
            Console.WriteLine($"添加到泛型缓存中的类型是:{generic_Cache}");
            return generic_Cache;
        }
    }
    //缓存实质上就是一个暂存区
    //测试
    public class CacheTest
    {
        public static void Show()
        {
            DictionaryCache.GetCache<int>();
            DictionaryCache.GetCache<string>();
            DictionaryCache.GetCache<int>();
            DictionaryCache.GetCache<DateTime>();
            DictionaryCache.GetCache<long>();

            Console.WriteLine();

            GenericCache<int>.GetCache();
            GenericCache<string>.GetCache();
            GenericCache<int>.GetCache();
            GenericCache<int>.GetCache();
            GenericCache<int>.GetCache();
            GenericCache<int>.GetCache();
            GenericCache<DateTime>.GetCache();
            GenericCache<long>.GetCache();
            GenericCache<double>.GetCache();
        }
    }

两种缓存执行结果如图所示:
泛型缓存测试执行结果

9.泛型约束

        public static void ShowObject(object oPar)
        {
            People people = (People)oPar;
            int Id = people.Id;
            string Name = people.Name;
            Console.WriteLine($"Id={Id},Name={Name}");
        }

上述代码中,参数类型是object,但实际上处理的是People类型的数据,其它类型的数据不可处理。
泛型约束的意义就在于解决这类的类型安全问题:传入的参数进行处理的时候,可能会出现类型不匹配的情况。
① 基类约束:where T:People
约束只能传入People及其子类
② 接口约束:where T:ISport
约束只能传入接口的实现类
③ 无参构造函数约束:where T:new()
约束只能传入具有无参构造函数的类
④ 值类型约束:where T:struct
约束只能传入值类型参数,引用类型参数无法传入
⑤ 引用类型约束:where T:class
约束只能传入引用类型参数,值类型参数无法传入
注意:
泛型支持同时进行多个约束,如where T:People,ISport,但是
① where T:class,People不对,因为class包含了People,约束内容不可以重复
② where T:class,struct不对,因为class和struct不允许同时出现,二者没有交集,同时约束就没有有符合条件的了,而且,class和struct在写约束时都规定必须放在第一个位置
以下代码定义了各个类、接口及其关系

        public interface ISports
        {
            void PingPang();
        }

        public interface IWork
        {
            void Work();
        }

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

            public string Name { get; set; }

            public virtual void Hi()
            {
                Console.WriteLine("你好!");
            }
        }

        public class Chinese : People, ISports, IWork
        {
            public void PingPang()
            {
                Console.WriteLine("Chinese 打乒乓球……");
            }

            public void Work()
            {
                Console.WriteLine("Chinese 工作……");
            }

            public override void Hi()
            {
                //base.Hi();
                Console.WriteLine("Chinese 你好……");
            }

            public void Trandition()
            {
                Console.WriteLine("仁义礼智信,温良恭俭让");
            }
        }

        public class ShanDong : Chinese
        {
            public string YellowRiver { get; set; }

            public void EatJianBing()
            {
                Console.WriteLine("吃煎饼……");
            }

            public override void Hi()
            {
                //base.Hi();
                Console.WriteLine("Shandong 你好!");
            }
        }

        public class Japanese : ISports
        {
            public int Id { get; set; }

            public string Name { get; set; }

            public void PingPang()
            {
                Console.WriteLine("Japanese 打乒乓球……");
            }
        }

泛型约束的应用

        /// <summary>
        /// 基类约束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public static void ShowBasic<T>(T t) where T : People
        {
            Console.WriteLine($"基类约束,Id={t.Id},Name={t.Name}");
            t.Hi();
        }

        /// <summary>
        /// 接口约束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public static void ShowInterface<T>(T t) where T : ISports
        {
            Console.WriteLine($"接口约束,传入的参数类型是:{typeof(T)},参数是:{t}");
        }

        /// <summary>
        /// 无参构造函数约束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public static void ShowNoParam<T>(T t) where T : new()
        {
            Console.WriteLine($"无参构造函数约束,传入的参数类型是:{typeof(T)},参数是:{t}");
        }

        /// <summary>
        /// 值类型约束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public static void ShowStruct<T>(T t) where T : struct
        {
            Console.WriteLine($"值类型约束,传入的参数类型是:{typeof(T)},参数是:{t}");
        }

        /// <summary>
        /// 引用类型约束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public static void ShowClass<T>(T t) where T : class
        {
            Console.WriteLine($"引用类型约束,传入的参数类型是:{typeof(T)},参数是:{t}");
        }

        /// <summary>
        /// 复合类型约束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        public static void ShowComplex<T>(T t) where T : People, IWork
        {
            Console.WriteLine($"复合类型约束,传入的参数类型是:{typeof(T)},参数是:{t}");
        }

应用测试

        public static void Show()
        {
            People people = new People
            {
                Id = 12,
                Name = "people"
            };

            Chinese chinese = new Chinese
            {
                Id = 23,
                Name = "chinese"
            };

            ShanDong shandong = new ShanDong
            {
                Id = 34,
                Name = "shandong"
            };

            Japanese japanese = new Japanese
            {
                Id = 45,
                Name = "japanese"
            };

            GenericConstraint.ShowBasic<People>(people);
            GenericConstraint.ShowBasic<Chinese>(chinese);
            GenericConstraint.ShowBasic<ShanDong>(shandong);
            Console.WriteLine();
            GenericConstraint.ShowInterface<Chinese>(chinese);
            GenericConstraint.ShowInterface<Japanese>(japanese);
            Console.WriteLine();
            GenericConstraint.ShowNoParam<People>(people);
            Console.WriteLine();
            GenericConstraint.ShowStruct<int>(123);
            Console.WriteLine();
            GenericConstraint.ShowClass<string>("234");
            GenericConstraint.ShowClass<ShanDong>(shandong);
            Console.WriteLine();
            GenericConstraint.ShowComplex<Chinese>(chinese);
        }

测试结果
泛型约束测试结果

10.泛型的协变、逆变

协变、逆变存在的意义是什么?看如下代码中的场景问题

        public static void Show2()
        {
            List<Animal> animals = null;
            animals = new List<Animal>();

            //List<Animal> animals2 = null;
            //animals2 = new List<Cat>();
            //一群猫一定是一群动物,但对于编译器而言,List<Animal>()和List<Cat>()是两个互不相干的类
            //泛型类在我们指定了类型之后,其实是一个新的类

            //为什么不能这么写?
            //因为两个互不相干的类,没有继承关系,在C#语法中,只能用父类声明子类的实例
            //一群猫明明就是一群动物,但编译器不允许我们这么写
            //这是泛型存在的不和谐的地方
            List<Animal> animals3 = new List<Cat>().Select(c => (Animal)c).ToList();
        }

- 协变
定义泛型参数时,out修饰符是协变的标志
out修饰的泛型参数只允许作为返回值参数,不允许作为方法参数使用
比如接口中的方法Show(T t)就将泛型参数作为方法参数了,协变约束的泛型参数不允许出现在方法参数中
协变的出现是为了解决“一群猫是一群动物”的问题
协变—>“和谐地变化”—>“很自然地变化”—>string===>object
协变其实是一种为了规避“风险”的约束
我理解的协变规避风险是在解决“一群猫就是一群动物”这个场景问题的前提下,通过协变来规避其中的风险,我们要带着这个场景去理解协变
协变是一种“规避风险”的约束,那么规避的是什么风险呢?

            ICustomerListOut<Animal> customerListOut1 = new CustomerListOut<Animal>();
            ICustomerListOut<Animal> customerListOut2 = new CustomerListOut<Cat>();//协变的体现
  • 如上代码,接口ICustomerListOut定义了泛型参数T,CustomerListOut作为接口实现类,必然实现了泛型参数T,“风险”就出自这两个泛型参数T,接口ICustomerListOut指定泛型参数T是Animal类,为了解决“一群猫就是一群动物”的问题,实现类指定泛型参数是Aniaml的子类Cat类,如果没有协变的约束,接口ICustomerListOut完全可以定义一个以泛型参数T作为方法参数的方法,如Show(T t),实现类也必然会实现这个方法,那么“风险来了”,由于接口ICustomerListOut指定的泛型参数类型是Animal,在没有协变的约束下,customerListOut2.Show(new Animal())也是可以的,但实现类指定的泛型参数类型却是Animal的子类Cat类,也就说Show(T t)方法中是由Cat类执行操作的,这就会出现强制将Animal类“向下转型”为Cat类的“类型安全风险”。协变的意义就在于规避这个“风险”,给泛型参数一个out修饰符,约束泛型参数T不可做为接口中的方法参数,如Show(T t)。协变实际上就是约束着不输入父类作为方法参数。
  • 以上是对协变逆向思维的分析理解,下面我们从正向思考理解协变。接口ICustomerListOut指定了T是Animal,但实现类CustomerListOut指定的T是Cat类型,在协变的约束下,泛型参数T只允许作为方法的返回参数使用,如 T GetOutType(),因此,实现类实际上返回的是Cat类,而Cat类是Animal的子类,根据里氏替换,任何父类出现的地方都可以用子类代替,从而规避了“类型安全风险”。
    - 逆变
    定义泛型参数时,in修饰符是逆变的标志
    in修饰的泛型参数T只允许作为方法参数,不可作为方法的返回值使用
    比如 T GetInType()方法就使得泛型参数作为返回值使用了,逆变约束的泛型参数不允许作为返回值
    逆变的出现是为了解决“这群动物就是一群猫”的场景问题
    逆变—>“逆常地变化”—>“不正常地变化”—>object===>string
    逆变其实是一种为了规避“风险”的约束
    对于逆变的理解要带着解决“这群动物就是一群猫”这个场景问题去理解。
    逆变是一种“规避风险”的约束,那么规避的“风险”是什么呢?
            ICustomerListIn<Cat> customerListIn1 = new CustomerListIn<Cat>();
            ICustomerListIn<Cat> customerListIn2 = new CustomerListIn<Animal>();//逆变的体现
  • 接口ICustomerListIn定义了泛型参数T,CustomerListIn作为实现类实现了泛型参数T,“风险”就出自这两个泛型参数T,如上代码,接口ICustomerListIn指定了泛型参数为Cat类型,而实现类CustomerListIn指定了泛型参数是Cat的父类Animal类型,如果没有逆变的约束,接口ICustomerListIn完全可以定义一个以泛型参数T作为返回值的方法,如 T GetInType(),实现类也必然会实现这个方法。那么“风险”来了,接口这边指定泛型参数时需要返回Cat类执行操作,实现类实际上返回的是Animal类型,这就导致了需要强制将Animal类向下转型为Cat类型,出现了“类型安全风险”,逆变的意义就在于规避这个“向下转型”的类型安全风险问题。此时,给泛型参数一个in修饰符形成逆变约束,使得泛型参数T不允许作为接口中的方法返回值参数使用,如 T GetInType(),逆变实际上就是约束着不允许返回父类。
  • 以上是我们逆向思维分析理解逆变,接下来,我们从正向分析理解逆变,接口ICustomerListIn指定了泛型参数T是Cat类,而实现类指定了泛型参数T是Animal类。在逆变的约束下,为了解决“这群动物是一群猫”的问题,ICustomerListIn只允许泛型参数T作为方法参数参与执行,如Show(T t),使得代码ICustomerListIn customerListIn2 = new CustomerListIn();成为合法,但在实际调用方法时,customerListIn2.Show(new Cat());只允许Cat类作为参数执行操作,这就避免了“向下转型”的类型安全“风险”。
    测试代码如下:
	public class Animal
    {
        public int Id { get; set; }
    }
    public class Cat:Animal
    {
        public string Name { get; set; }
    }
    interface ICustomerListOut<out T>
    {
        T GetOutType();
    }
    interface ICustomerListIn<in T>
    {
        void Show(T t);
    }
    public class CustomerListOut<T>:ICustomerListOut<T>
    {
        public T GetOutType()
        {
            Console.WriteLine($"协变测试类 CustomerListOut 返回的参数类型是:{typeof(T).FullName}");
            return default(T);
        }
    }
    public class CustomerListIn<T>:ICustomerListIn<T>
    {
        public void Show(T t)
        {
            Console.WriteLine($"逆变测试类 CustomerListIn 传入的参数类型是:{typeof(T).FullName},参数是:{t}");
        }
    }
    interface IMyList<in T1,out T2>
    {
        void Show(T1 t);

        T2 GetOutType();

        T2 ShowComplex(T1 t);
    }
    public class MyList<T1,T2>:IMyList<T1,T2>
    {
        public void Show(T1 t)
        {
            Console.WriteLine($"逆变协变复合测试类 Show 方法参数类型:{typeof(T1).FullName},方法参数是:{t}");
        }

        public T2 GetOutType()
        {
            Console.WriteLine($"逆变协变复合测试类 GetOutType 返回参数类型:{typeof(T1).FullName}");
            return default(T2);
        }

        public T2 ShowComplex(T1 t)
        {
            Console.WriteLine($"逆变协变复合测试类 ShowComplex 方法参数类型:{typeof(T1).FullName},方法参数是:{t},返回参数类型是:{typeof(T2)}");
            return default(T2);
        }
    }

    public static void Show3()
    {
        //协变就可以解决“一群猫就是一群动物”的问题了
        IEnumerable<Animal> animalsList1 = new List<Animal>();
        IEnumerable<Animal> animalsList2 = new List<Cat>();

        ICustomerListOut<Animal> customerListOut1 = new CustomerListOut<Animal>();
        ICustomerListOut<Animal> customerListOut2 = new CustomerListOut<Cat>();//协变的体现

        customerListOut2.GetOutType();

        Console.WriteLine();

        ICustomerListIn<Cat> customerListIn1 = new CustomerListIn<Cat>();
        ICustomerListIn<Cat> customerListIn2 = new CustomerListIn<Animal>();//逆变的体现
        customerListIn2.Show(new Cat());
    }

    public static void Show4()
    {
        IMyList<Cat, Animal> mylist = new MyList<Animal, Cat>();
        mylist.Show(new Cat());
        mylist.GetOutType();
        mylist.ShowComplex(new Cat());
    }

测试结果如下:
泛型的协变逆变

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值