C#高级--泛型详解

C#高级–泛型详解

零、文章目录

一、泛型是什么

泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。

我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的。

使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:

  • 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
  • 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
  • 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
  • 您可以对泛型类进行约束以访问特定数据类型的方法。
  • 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。

二、为什么使用泛型

1、不同类型参数,重复写多个方法

方法实现

using System;

namespace MyGeneric
{
    public class CommonMethod
    {
        /// 
        /// 打印个int值
        /// 声明方法时,指定了参数类型,确定了只能传递某个类型
        /// 
        /// 
        public static void ShowInt(int iParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);
        }

        /// 
        /// 打印个string值
        /// 
        /// 
        public static void ShowString(string sParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);
        }

        /// 
        /// 打印个DateTime值
        /// 
        /// 
        public static void ShowDateTime(DateTime dtParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);
        }
    }
}

方法调用

using System;
using System.Collections.Generic;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            {                
                int iValue = 123;
                string sValue = "456";
                DateTime dtValue = DateTime.Now;

                Console.WriteLine("***********************Common***********************");
                CommonMethod.ShowInt(iValue);
                CommonMethod.ShowString(sValue);
                CommonMethod.ShowDateTime(dtValue);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

运行结果

***********************Common***********************
This is CommonMethod,parameter=Int32,type=123
This is CommonMethod,parameter=String,type=456
This is CommonMethod,parameter=DateTime,type=2021/7/25 19:30:52

代码分析

从上面的结果中我们可以看出这三个方法,除了传入的参数不同外,实现的功能都是一样的。在C#1.0版的时候,还没有泛型这个概念,那么怎么办呢。相信很多人会想到了OOP三大特性之一的继承,我们知道,C#语言中,object是所有类型的基类,将上面的代码进行以下优化。

2、用object类型参数,只需要写一个方法

方法实现

using System;

namespace MyGeneric
{
    public class CommonMethod
    {
        /// 
		/// 打印个object值
		/// 
		/// 
		public static void ShowObject(object oParameter)
		{
    		Console.WriteLine("This is {0},parameter={1},type={2}",
        		typeof(CommonMethod), oParameter.GetType().Name, oParameter);
		}
    }
}

方法调用

using System;
using System.Collections.Generic;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            {                
                int iValue = 123;
                string sValue = "456";
                DateTime dtValue = DateTime.Now;
                object oValue = "678";

                Console.WriteLine("***********************Object***********************");
                CommonMethod.ShowObject(oValue);
                CommonMethod.ShowObject(iValue);
                CommonMethod.ShowObject(sValue);
                CommonMethod.ShowObject(dtValue);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

运行结果

***********************Object***********************
This is MyGeneric.CommonMethod,parameter=String,type=678
This is MyGeneric.CommonMethod,parameter=Int32,type=123
This is MyGeneric.CommonMethod,parameter=String,type=456
This is MyGeneric.CommonMethod,parameter=DateTime,type=2021/7/25 19:40:51

代码分析

从上面的结果中我们可以看出,使用Object类型达到了我们的要求,解决了代码的可复用。

object类型的,为什么可以传入int、string等类型呢?

  • object类型是一切类型的父类。
  • 通过继承,子类拥有父类的一切属性和行为,任何父类出现的地方,都可以用子类来代替。

但是上面object类型的方法又会带来另外一些问题

  • 装箱和拆箱,会损耗程序的性能。
  • 类型安全问题。

于是微软在C#2.0的时候推出了泛型,可以很好的解决上面的问题。

3、泛型方法,只需写一个方法

方法实现

using System;

namespace MyGeneric
{
    public class GenericMethod
    {
        /// 
        /// 
        /// 泛型方法:方法名称后面加上尖括号,里面是类型参数
        ///           类型参数实际上就是一个类型T声明,方法就可以用这个类型T了
        /// 
        /// 
        /// 
        public static void Show(T tParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
               typeof(GenericMethod), tParameter.GetType().Name, tParameter);
        }
    }
}

方法调用

using System;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            {                
                int iValue = 123;
                string sValue = "456";
                DateTime dtValue = DateTime.Now;
                object oValue = "678";

                Console.WriteLine("***********************Generic***********************");
                GenericMethod.Show(iValue);//调用泛型,需要指定类型参数
                GenericMethod.Show(iValue);//如果可以从参数类型推断,可以省略类型参数---语法糖(编译器提供的功能)
                GenericMethod.Show(sValue);
                //GenericMethod.Show(sValue);//类型错了
                GenericMethod.Show(dtValue);
                GenericMethod.Show(oValue);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}
 
  

运行结果

***********************Generic***********************
This is MyGeneric.GenericMethod,parameter=Int32,type=123
This is MyGeneric.GenericMethod,parameter=Int32,type=123
This is MyGeneric.GenericMethod,parameter=String,type=456
This is MyGeneric.GenericMethod,parameter=DateTime,type=2021/7/25 22:24:35
This is MyGeneric.GenericMethod,parameter=String,type=678

代码分析

在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定的特定类型的占位符。 泛型类( GenericList)无法按原样使用,因为它不是真正的类型;它更像是类型的蓝图。 若要使用 GenericList,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。 可创建任意数量的构造类型实例,其中每个使用不同的类型参数。

三、泛型运行原理

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

泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。 延迟思想在程序架构设计的时候很受欢迎。例如:分布式缓存队列、EF的延迟加载等等。

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

控制台程序最终会编译成一个exe程序,exe被点击的时候,会经过JIT(即时编译器)的编译,最终生成二进制代码,才能被计算机执行。泛型加入到语法以后,VS自带的编译器又做了升级,升级之后编译时遇到泛型,会做特殊的处理:生成占位符。再次经过JIT编译的时候,会把上面编译生成的占位符替换成具体的数据类型。

方法调用

using System;
using System.Collections.Generic;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            { 
                Console.WriteLine(typeof(List<>));
                Console.WriteLine(typeof(Dictionary<,>));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

运行结果

System.Collections.Generic.List`1[T]
System.Collections.Generic.Dictionary`2[TKey,TValue]

代码分析

从上面的截图中可以看出:泛型在编译之后会生成占位符。

注意:占位符需要在英文输入法状态下才能输入,只需要按一次波浪线(数字1左边的键位)的键位即可,不需要按Shift键。

3、泛型性能怎么样?

比较普通方法、Object参数类型的方法、泛型方法的性能。

方法实现

添加一个Monitor类,让三种方法执行同样的操作,比较用时长短:

using System;
using System.Diagnostics;

namespace MyGeneric
{
    /// 
    /// 性能对比
    /// 
    public class Monitor
    {
        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 < 100_000_000; i++)
                    {
                        ShowInt(iValue);
                    }
                    watch.Stop();
                    commonSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100_000_000; i++)
                    {
                        ShowObject(iValue);
                    }
                    watch.Stop();
                    objectSecond = watch.ElapsedMilliseconds;
                }
                {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();
                    for (int i = 0; i < 100_000_000; i++)
                    {
                        Show(iValue);
                    }
                    watch.Stop();
                    genericSecond = watch.ElapsedMilliseconds;
                }
                Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
                    , commonSecond, objectSecond, genericSecond);
            }
        }
        
        private static void ShowInt(int iParameter)
        {
            //do nothing
        }
        private static void ShowObject(object oParameter)
        {
            //do nothing
        }
        private static void Show(T tParameter)
        {
            //do nothing
        }
    }
}

方法调用

using System;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            { 
                Monitor.Show();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

运行结果

****************Monitor******************
commonSecond=325,objectSecond=644,genericSecond=347

代码分析

从结果中可以看出:泛型方法的性能最高,其次是普通方法,object方法的性能最低。

泛型的好处是速度快且支持不同类型。

四、泛型应用范围

1、泛型方法

using System;

namespace MyGeneric
{
    public class GenericMethod
    {
        /// 
        /// 泛型方法:为了一个方法满足不同的类型的需求
        ///           一个方法完成多实体的查询
        ///           一个方法完成不同的类型的数据展示
        /// 多类型参数:不要关键字,不要类名称重复
        /// 
        /// 
        /// 
        public T Get(Eleven eleven)
        {
            GenericClass genericClass = new GenericClass();
            throw new Exception();
        }
    }
}

2、泛型类

泛型类定义

namespace MyGeneric
{
    /// 
    /// 泛型类
    /// 
    /// 
    public class GenericClass
    {
        public T _T;
    }
}

泛型类使用

using System;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            { 
                // T是int类型
                GenericClass genericInt = new GenericClass();
                genericInt._T = 123;
                // T是string类型
                GenericClass genericString = new GenericClass();
                genericString._T = "123";
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

3、泛型接口

namespace MyGeneric
{
    /// 
    /// 泛型接口
    /// 
    /// 
    public interface IGenericInterface
    {
        //泛型类型的返回值
        T GetT(T t);
    }
}

4、泛型委托

public delegate void SayHi(T t);//泛型委托

5、泛型类继承父类,实现接口

如果子类不是泛型的,那么继承的时候必须指定具体类型

namespace MyGeneric
{
    /// 
    /// 使用泛型的时候必须指定具体类型,这里的具体类型是int
    /// 
    public class CommonClass : GenericClass
    {
    }
}

如果子类也是泛型的,那么继承的时候可以不指定具体类型

namespace MyGeneric
{
    /// 
    /// 子类也是泛型的,继承的时候可以不指定具体类型
    /// 
    /// 
    public class CommonClassChild : GenericClass
    {
    }
}

如果子类不是泛型的,那么实现泛型接口的时候必须指定具体类型

namespace MyGeneric
{
    /// 
    /// 必须指定具体类型
    /// 
    public class Common : IGenericInterface
    {
        public string GetT(string t)
        {
            throw new System.NotImplementedException();
        }
    }
}

如果子类也是泛型的,那么实现泛型接口的时候可以不指定具体类型

namespace MyGeneric
{
    /// 
    /// 可以不知道具体类型,但是子类也必须是泛型的
    /// 
    /// 
    public class CommonChild : IGenericInterface
    {
        public T GetT(T t)
        {
            throw new NotImplementedException();
        }
    }
}

五、泛型约束

1、为什么要有泛型约束?

因为有约束才有权利,自由主义的鼻祖洛克先生说过,有了法律,才有自由

定义类,属性,方法

using System;

namespace MyGeneric
{
    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 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("打乒乓球...");
        }
    }
}

定义方法

using System;

namespace MyGeneric
{
    /// 
    /// 泛型约束
    /// 
    public class GenericConstraint
    {
        /// 
        /// object参数
        /// 
        /// 
        public static void ShowObject(object oParameter)
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
                typeof(GenericConstraint), oParameter.GetType().Name, oParameter);
            People people = (People)oParameter;
            Console.WriteLine($"{people.Id}  {people.Name}");
        }
    }
}

调用方法

using System;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            { 
                People people = new People()
                {
                    Id = 123,
                    Name = "Jon"
                };
                Chinese chinese = new Chinese()
                {
                    Id = 234,
                    Name = "一生为你"
                };
                Hubei hubei = new Hubei()
                {
                    Id = 345,
                    Name = "木头"
                };
                Japanese japanese = new Japanese()
                {
                    Id = 456,
                    Name = "苍老师"
                };

                GenericConstraint.ShowObject(people);
                GenericConstraint.ShowObject(chinese);
                GenericConstraint.ShowObject(hubei);
                //没有约束,任何类型都能传递进来,所以可能不安全
                GenericConstraint.ShowObject(japanese);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

运行结果

This is MyGeneric.GenericConstraint,parameter=People,type=MyGeneric.People
123  Jon
This is MyGeneric.GenericConstraint,parameter=Chinese,type=MyGeneric.Chinese
234  一生为你
This is MyGeneric.GenericConstraint,parameter=Hubei,type=MyGeneric.Hubei
345  木头
This is MyGeneric.GenericConstraint,parameter=Japanese,type=MyGeneric.Japanese
无法将类型为“MyGeneric.Japanese”的对象强制转换为类型“MyGeneric.People”。

代码分析

可以看出程序报错了,因为Japanese没有继承自People,这里类型转换的时候失败了。这样会造成类型不安全的问题。那么怎么解决类型不安全的问题呢?那就是使用泛型约束。

所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。那么怎么给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。

添加泛型约束后代码实现

using System;

namespace MyGeneric
{
    /// 
    /// main方法调用
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                People people = new People()
                {
                    Id = 123,
                    Name = "Jon"
                };
                Chinese chinese = new Chinese()
                {
                    Id = 234,
                    Name = "一生为你"
                };
                Hubei hubei = new Hubei()
                {
                    Id = 345,
                    Name = "木头"
                };
                Japanese japanese = new Japanese()
                {
                    Id = 456,
                    Name = "苍老师"
                };

                GenericConstraint.ShowP(people);
                GenericConstraint.ShowP(chinese);
                GenericConstraint.ShowP(hubei);
                //japanese不是people或者people的子类,所以编译直接报错
                //GenericConstraint.ShowP(japanese);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

添加泛型约束后运行结果

This is MyGeneric.GenericConstraint,parameter=People,type=MyGeneric.People
123  Jon
This is MyGeneric.GenericConstraint,parameter=Chinese,type=MyGeneric.Chinese
234  一生为你
This is MyGeneric.GenericConstraint,parameter=Hubei,type=MyGeneric.Hubei
345  木头

添加泛型约束后代码分析

代码直接通不过编译,增强代码的安全性

2、五种泛型约束

约束 s说明
T:结构 类型参数必须是值类型
T:类 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。
T:new() 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。
T:<基类名> 类型参数必须是指定的基类或派生自指定的基类。
T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。

(1)基类约束

代码实现

using System;

namespace MyGeneric
{
    /// 
    /// 泛型约束
    /// 
    public class GenericConstraint
    {
        /// 
        /// 基类约束
        /// 
        /// 
        /// 
        public static void ShowP(T tParameter) where T : People
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
               typeof(GenericConstraint), tParameter.GetType().Name, tParameter);

            Console.WriteLine($"{tParameter.Id}  {tParameter.Name}");
            tParameter.Hi();
        }
    }
}

代码说明

基类约束保证T必须是类或者子类

基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义,因为sealed类没有子类。

(2)接口约束

代码实现

using System;

namespace MyGeneric
{
    /// 
    /// 泛型约束
    /// 
    public class GenericConstraint
    {
        /// 
        /// 接口约束
        /// 
        /// 
        /// 
        public static void ShowPI(T tParameter) where T : ISports
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
               typeof(GenericConstraint), tParameter.GetType().Name, tParameter);

            tParameter.Pingpang();
        }
    }
}

代码说明

接口约束保证T必须实现接口

(3)引用类型约束 class

代码实现

using System;

namespace MyGeneric
{
    /// 
    /// 泛型约束
    /// 
    public class GenericConstraint
    {
        /// 
        /// 引用类型约束
        /// 
        /// 
        /// 
        /// 
        public static T Get(T t) where T : class
        {
            return t;
        }
    }
}

代码说明

引用类型约束保证T一定是引用类型的。

(4)值类型约束 struct

代码实现

using System;

namespace MyGeneric
{
    /// 
    /// 泛型约束
    /// 
    public class GenericConstraint
    {
        /// 
        /// 值类型类型约束
        /// 
        /// 
        /// 
        /// 
        public static T GetStruct(T t) where T : struct
        {
            return t;
        }
    }
}

代码说明

值类型约束保证T一定是值类型的。

(5)无参数构造函数约束 new()

代码实现

using System;

namespace MyGeneric
{
    /// 
    /// 泛型约束
    /// 
    public class GenericConstraint
    {
        /// 
        /// 值类型类型约束
        /// 
        /// 
        /// 
        /// 
        public static T GetNew(T t) where T : new()
        {
            return t;
        }
    }
}

代码说明

无参数构造函数约束保证T必须有无参数构造函数

3、泛型约束可以同时约束多个

代码实现

using System;

namespace MyGeneric
{
    /// 
    /// 泛型约束
    /// 
    public class GenericConstraint
    {
        /// 
        /// 泛型约束也可以同时约束多个
        /// 
        /// 
        /// 
        public static void Show(T tParameter) where T : People, ISports, IWork, new()
        {
            Console.WriteLine("This is {0},parameter={1},type={2}",
               typeof(GenericConstraint), tParameter.GetType().Name, tParameter);

            Console.WriteLine($"{tParameter.Id}  {tParameter.Name}");
            tParameter.Hi();
        }
    }
}

代码说明

有多个泛型约束时,new()约束一定是在最后。

六、泛型的协变和逆变

1、泛型的协变和逆变是什么?

协变和逆变是在.NET 4.0的时候出现的,只能放在接口或者委托的泛型参数前面,out 协变covariant,用来修饰返回值;in:逆变contravariant,用来修饰传入参数。

2、为什么要用泛型的协变?

定义一个Bird类, 然后再定义一个Sparrow类继承自Bird类

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyGeneric
{
    public class Bird
    {
        public int Id { get; set; }
    }
    public class Sparrow : Bird
    {
        public string Name { get; set; }
    }
}

代码调用

Bird bird1 = new Bird();
Bird bird2 = new Sparrow();
Sparrow sparrow1 = new Sparrow();
List birdList1 = new List();
List sparrowList1 = new List();

代码分析

下面的一句代码是不是正确的呢?

List sparrowList1 = new List();

可能有人会认为是正确的:因为一只Sparrow属于Bird,那么一群Sparrow也应该属于一群Bird啊。但是实际上这样声明是错误的:因为List和List之间没有父子关系。

3、泛型协变定义

协变代码实现

// 协变
IEnumerable birdList1 = new List();
IEnumerable birdList2 = new List();

F12查看IEnumerable定义:

#region 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll
// Decompiled with ICSharpCode.Decompiler 6.1.0.5902
#endregion

using System.Runtime.CompilerServices;

namespace System.Collections.Generic
{
    [TypeDependency("System.SZArrayHelper")]
    [__DynamicallyInvokable]
    public interface IEnumerable : IEnumerable
    {
        [__DynamicallyInvokable]
        new IEnumerator GetEnumerator();
    }
}
#if false // 反编译日志
缓存中的 9 项
#endif

代码分析

可以看到,在泛型接口的T前面有一个out关键字修饰,而且T只能是返回值类型,不能作为参数类型,这就是协变。使用了协变以后,左边声明的是基类,右边可以声明基类或者基类的子类。

4、泛型协变用在委托上

协变除了可以用在接口上面,也可以用在委托上面:

//委托协变
Func func = new Func(() => null);

5、自定义泛型协变

定义协变

/// 
/// out 协变 只能是返回结果, T在类里面就不能做方法参数,做参数会报错
/// 
/// 
public interface ICustomerListOut
{
    T Get();
}

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

使用协变

//自定义协变
ICustomerListOut customerList1 = new CustomerListOut();
ICustomerListOut customerList2 = new CustomerListOut();

6、自定义泛型逆变

定义泛型逆变

/// 
/// in 逆变
/// 
/// 
public interface ICustomerListIn
{
    void Show(T t);
}

public class CustomerListIn : ICustomerListIn
{
    public void Show(T t)
    {

    }
}

使用自定义逆变

//自定义逆变
ICustomerListIn customerList2 = new CustomerListIn();
ICustomerListIn customerList1 = new CustomerListIn();

代码说明

在泛型接口的T前面有一个In关键字修饰,而且T只能方法参数,不能作为返回值类型,这就是逆变。

7、协变和逆变同时使用

代码实现

/// 
/// inT  逆变
/// outT 协变
/// 
/// 
/// 
public interface IMyList
{
    void Show(inT t);
    outT Get();
    outT Do(inT t);
}

public class MyList : IMyList
{
    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);
    }
}

代码调用

IMyList myList1 = new MyList();
IMyList myList2 = new MyList();//协变
IMyList myList3 = new MyList();//逆变
IMyList myList4 = new MyList();//协变+逆变

七、泛型缓存

1、常见字典缓存实现

代码实现

/// 
/// 字典缓存:静态属性常驻内存
/// 
public class DictionaryCache
{
    private static Dictionary _TypeTimeDictionary = null;
    static DictionaryCache()
    {
        Console.WriteLine("This is DictionaryCache 静态构造函数");
        _TypeTimeDictionary = new Dictionary();
    }
    public static string GetCache()
    {
        Type type = typeof(Type);
        if (!_TypeTimeDictionary.ContainsKey(type))
        {
            _TypeTimeDictionary[type] = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
        }
        return _TypeTimeDictionary[type];
    }
}

代码说明

类中的静态类型无论实例化多少次,在内存中只会有一个。静态构造函数只会执行一次。

2、泛型缓存实现

代码实现

/// 
/// 每个不同的T,都会生成一份不同的副本
/// 适合不同类型,需要缓存一份数据的场景,效率高
/// 
/// 
public class GenericCache
{
    static GenericCache()
    {
        Console.WriteLine("This is GenericCache 静态构造函数");
        _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
    }

    private static string _TypeTime = "";

    public static string GetCache()
    {
        return _TypeTime;
    }
}

然后新建一个测试类,用来测试GenericCache类的执行顺序

/// 
/// 泛型缓存
/// 
public class GenericCacheTest
{
    public static void Show()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(GenericCache.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache.GetCache());
            Thread.Sleep(10);
            Console.WriteLine(GenericCache.GetCache());
            Thread.Sleep(10);
        }
    }
}

调用方法

GenericCacheTest.Show();

运行结果

*******************GenericCache********************
This is GenericCache 静态构造函数
System.Int32_20210213230031.439
This is GenericCache 静态构造函数
System.Int64_20210213230031.463
This is GenericCache 静态构造函数
System.DateTime_20210213230031.479
This is GenericCache 静态构造函数
System.String_20210213230031.495
This is GenericCache 静态构造函数
MyGeneric.GenericCacheTest_20210213230031.511
System.Int32_20210213230031.439
System.Int64_20210213230031.463
System.DateTime_20210213230031.479
System.String_20210213230031.495
MyGeneric.GenericCacheTest_20210213230031.511
System.Int32_20210213230031.439
System.Int64_20210213230031.463
System.DateTime_20210213230031.479
System.String_20210213230031.495
MyGeneric.GenericCacheTest_20210213230031.511
System.Int32_20210213230031.439
System.Int64_20210213230031.463
System.DateTime_20210213230031.479
System.String_20210213230031.495
MyGeneric.GenericCacheTest_20210213230031.511
System.Int32_20210213230031.439
System.Int64_20210213230031.463
System.DateTime_20210213230031.479
System.String_20210213230031.495
MyGeneric.GenericCacheTest_20210213230031.511

代码分析

从上面的截图中可以看出,泛型会为不同的类型都创建一个副本,所以静态构造函数会执行5次。 而且每次静态属性的值都是一样的。利用泛型的这一特性,可以实现缓存。

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

你可能感兴趣的:(C#.Net,c#,泛型)