泛型的学习

泛型的学习

一、泛型的引入

  1. 泛型---泛:宽泛的--不确定的; 型:类型---不确定的类型
  2. 无处不在的
  3. 调用普通方法的时候,参数类型在声明的时候就确定了,调用按照类型传递参数即可
    a. 如果有100个类型---100个方法?--很累
    b. 有没有能够做一个方法可以能够满足不同类型的需求呢?

传统方法

   public static   class CommonMethod
    {
        public static void ShowInt(int Parameter)
        {
            Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
        }

        public static void ShowString(string Parameter)
        {
            Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
        }

        public static void ShowDateTime(DateTime Parameter)
        {
            Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
        }     
    }
  1. Object类型作为参数 ----可以传递不同的参数
    a. 任何子类出现的地址都可以让父类来代替
    b. 万物皆对象---任何一个类型都是继承自Object

使用Object类型

   public static   class CommonMethod
    {
        public static void ShowObject(object Parameter)
        {
            Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
        }
    }
  1. 问题
    a. 性能问题 ---装箱拆箱---在c#语法中,按照声明时决定类型的(栈、托管堆)
    b. 类型安全问题

我们通过一个例子来体现性能的问题

public  class PerformanceTest
    {
        public static void Show()
        {
            int ivalue = 1234;
            //消耗的时间
            long commonSecond = 0;
            long objectSecond = 0;
            long genericSecond = 0;

            {
                Stopwatch sw = new Stopwatch(); 
                sw.Start();
                for (int i = 0; i < 1000_000_000; i++) {
                    ShowInt(ivalue);
                }
               sw.Stop();
                commonSecond = sw.ElapsedMilliseconds;
            }

            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                for (int i = 0; i < 1000_000_000; i++)
                {
                    ShowObject(ivalue);
                }
                sw.Stop();
                objectSecond = sw.ElapsedMilliseconds;
            }

            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                for (int i = 0; i < 1000_000_000; i++)
                {
                    Show(ivalue);
                }
                sw.Stop();
                genericSecond = sw.ElapsedMilliseconds;
            }
            Console.WriteLine($"commonSecond: {commonSecond} objectSecond: {objectSecond} genericSecond :{genericSecond} ");
        }


        


        private static void ShowInt(int r)
        {

        }

        private static void ShowObject(object o)
        {

        }


         private static void Show<T>(T parameter)
        {

        }

    }

结果为:

  1. 有没有既性能好,也能够支持多种类型的方法呢?---泛型方法
    a. 声明多了一对尖括号 + 占位符T
    b. 调用--也需要多一对尖括号,尖括号中指定的类型要和传递的参数的类型一致。
    c. 如果可以参数推到出类型---尖括号可以省略。
  2. 泛型方法---做到了性能高---可以一个方法满足不同类的需求:---又让马儿跑,又让马吃草

二、泛型的声明

1.泛型方法:在方法名称后面多一对尖括号,尖括号中有占位符
2.延迟声明:声明的时候,只是给一个占位符T,T是什么类型?不知道什么类型---调用的时候,指定你是什么,调用的时候,你说什么就是什么;
3.占位符: T ---类型参数 --- 类型变量
4.类型参数当作方法的参数的时候,明确参数类型。

三、泛型的特点+原理 -- 底层如何支持的?

  1. 在高级语言中,定义的泛型T,在计算机执行的时候一定要是一个具体的类型。
  2. 在底层如何支持?---在底层看到,生成的结果是LIST1[T] Dictionary2[TKey,TValue]
  3. 在底层---生成了1、2、3、4、5、6
  4. 编译器必须要能够支持泛型
  5. CLR运行时环境也需要要支持泛型
  6. 泛型当然是框架的升级支持的---泛型不是语法糖---有框架的升级支持的;
  7. 语法糖:是编译器提供的便捷功能。

四、泛型的多种应用

  1. 泛型方法---可以一个方法满足不同类型的需求
  2. 泛型接口---可以一个接口满足不同类型的需求 --- 尖括号+占位符
  3. 泛型类-----可以一个类型满足不同类型的需求
  4. 泛型委托---可以一个委托满足不同类型的需求
    尖括号中可以有多个类型参数。
public class GenericsTest
    {
        public interface GenericsInterface<T>
        {
            public T show();
        }
        public class GenericsClass<T>
        {
            public void Show(T t)
            { 
                Console.WriteLine(t); 
            }
        }

        public delegate T genericsDelegate<T>();

        //1.泛型方法
        static void Show<T>(T value)
        {
            Console.WriteLine(value);
        }
        
        //2.泛型接口
        GenericsInterface<string> genericsString = null;
        GenericsInterface<DateTime> genericsDatetime = null;
       
        //3.泛型类
        GenericsClass<string> genericsClassString = null;
        GenericsClass<DateTime> genericsClassDateTime = null;

        //4.泛型委托
        genericsDelegate<string> genericsDelegateString=null;
        genericsDelegate<DateTime> genericsDelegateDateTime=null;

    }

类型参数一定要为T吗?不一定,也可以是其他的名字(不要使用关键字)。
并且尖括号中可以有多个类型参数

  public class GenericsClass<T,S,X,GODLWL>
        {
            public void Show(T t)
            { 
                Console.WriteLine(t); 
            }
        }

继承抽象泛型类的情况

     //使用T报错,为什么不行?要继承的时候,必须要确定类型,
        public abstract class Children:FatherClass<T>
        {

        }
      //不报错,在子类实例化的时候,父类的类型也会被确定。
         public class Children1<S> : FatherClass<S>
        {

        }

继承类的状况

   public class Children2<S>
        {
            public S show()
            {
                return default(S); 
            }

            public S show(S s)
            {
                return s;
            }
        }

五、类型安全解读

要有真正的自由--就需要要有约束--开车---交通规则--红绿灯

  1. Object类型
 public class Genericconstraints
    {

        public class People
        {
            public int Id { get; set; }
            public string Name { get; set; }  
        }
        public static void ShowObject(object oValue)
        {
            //没有意义的打印输出
            //Console.WriteLine(oValue);
            //传递一个实体的对象:操作字段和方法
            //问题:
            //1.无法去属性字段--因为oValue是Object;C#是强类型语言,编译时决定参数是什么类型;
            //Console.WriteLine($"People.Id={oValue.Id}");
            //Console.WriteLine($"People.Name={oValue.Name}");
            //2、强制转换
            People people = (People)oValue;
            Console.WriteLine($"People.Id={people.Id}");
            Console.WriteLine($"People.Name={people.Name}");
        }
    }

当我们传递一个INT类型的变量的时候,运行的时候就会报错。
为了避免这种类型安全的问题,我们就引入了泛型的约束。

六、泛型的约束

  1. 如何避免泛型的类型安全问题,引入了泛型约束。
 //基类约束
        //a.就是把类型参数当做People
        //b.调用---就可以传递People或者People的子类型
        //c.泛型约束:要么不让你进来;如果让你进来,就一定是没问题的
        public static void Show<T>(T tValue) where T : People
        {
            Console.WriteLine($"People.Id={tValue.Id}");
            Console.WriteLine($"People.Name={tValue.Name}");
        }
    public interface ISports
        {
            public void run();
        }
        public class Cat:ISports
        {
            public void run()
            {
                Console.WriteLine("Cat run");
            }
        }
        //接口约束
        // a.把这个T当做ISports
        // b.就只能传递ISports这个接口或者实现这个接口的类
        // c.就可以增加功能,也可以获取新的功能。
        public static void ShowInterface<T>(T tValue) where T : ISports
        {
            tValue.run();
        }

  //引用类型约束
        // a.就只能传递类型进来
        public static void ShowClass<T>(T tValue) where T : class
        {
            
        }

   //值类型约束
        // a.只能传递值类型进来
        public static void ShowValue<T>(T tValue) where T : struct
        {

        }

    //无参数构造函数约束
        //
        public static void ShowNew<T>(T tValue) where T : new()
        {
            T t= new T();
        }

      //枚举约束
        public static void ShowEnum<T>(T tValue) where T : Enum
        {

        }

七、泛型缓存---泛型类

泛型缓存可以根据不同的类型生成一个新的类的副本;生成无数个副本;

 public class GenericCacheTest
    {
        public static void Show()
        {

            //普通缓存
            {
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine(DictionaryCache.GetCache<int>()); //GenericCacheInt
                    Thread.Sleep(10);
                    Console.WriteLine(DictionaryCache.GetCache<long>());// GenericCachelong
                    Thread.Sleep(10);
                    Console.WriteLine(DictionaryCache.GetCache<DateTime>());
                    Thread.Sleep(10);
                    Console.WriteLine(DictionaryCache.GetCache<string>());
                    Thread.Sleep(10);
                    Console.WriteLine(DictionaryCache.GetCache<GenericCacheTest>());
                    Thread.Sleep(10);
                }
            }

            //泛型缓存
            {
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine(GenericCache<int>.GetCache()); //GenericCacheInt
                    Thread.Sleep(10);
                    Console.WriteLine(GenericCache<long>.GetCache());// GenericCachelong
                    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);
                }
            }
        }
    }

    /// <summary>
    /// 字典缓存:静态属性常驻内存
    /// </summary>
    public class DictionaryCache
    {
        private static Dictionary<Type, string> _TypeTimeDictionary = null;

        //静态构造函数在整个进程中,执行且只执行一次;
        static DictionaryCache()
        {
            Console.WriteLine("This is DictionaryCache 静态构造函数");
            _TypeTimeDictionary = new Dictionary<Type, string>();
        }
        public static string GetCache<T>()
        {
            Type type = typeof(T);
            if (!_TypeTimeDictionary.ContainsKey(type))
            {
                _TypeTimeDictionary[type] = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
            }
            return _TypeTimeDictionary[type];
        }
    }


    /// <summary>
    ///泛型缓存:
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GenericCache<T>
    {
        static GenericCache()
        {
            Console.WriteLine("This is GenericCache 静态构造函数");
            //_TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); 
            _TypeTime = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
        }

        private static string _TypeTime = "";

        public static string GetCache()
        {
            return _TypeTime;
        }

    }

七、泛型的协变和逆变

比如我们有一个动物类以及它的子类猫类

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

    }

    public class Cat : Animal {
        public string Name { get; set; }
    }

我们可以使用这样的代码

//任何子类都可以使用父类来声明
Animal animal1 = new Cat();
//不一定能用子类来声明父类
Cat cat2 = new Animal();

我们还会遇到这样的问题
一只猫是一堆动物
一堆猫却不是一堆动物---从口语上来说,有点不符合人类的逻辑的思维。

List<Animal> animals = new List<Animal>();
//报错
List<Animal> animals2 = new List<Cat>();

为什么?---二类没有父子级关系;当然不能替换;这是C#语法所决定的。
泛型存在不友好,不协调的地方;

这样就引入我们的协变和逆变;
协变逆变 只针对于泛型接口和泛型委托

//协变 就可以让右边使用子类,能让左边用父类
//out:修饰类型参数:就可以让右边用子类,能让左边用父类
IEnumerable<Animal> animals1=new List<Animal>();
IEnumerable<Animal> animals3= new List<Cat>();

协变: Out类型参数只能做返回值,不能做参数

逆变In 只能做参数,不能做返回值

 public interface ICustomerListIn<in T>
    {
        void show(T t);
    }

    public class CustomerListIn<T>:ICustomerListIn<T>
    {
        public void show(T t)
        {

        }
    }

逆变的代码例子

//逆变:就可以让右边用父类;左边用子类;
ICustomerListIn<Cat> customerListIn = new CustomerListIn<Animal>();
ICustomerListIn<Cat> customerListIn1 = new CustomerListIn<Cat>();

为什么要有协变和逆变?
如果没有协变和逆变会怎么样?


 public interface ICustomerList<T> {
        T Get();

        void show(T t);
    }

    public class CustomerList<T>:ICustomerList<T>
    {
        public T Get()
        {
            return default(T);
        }
        public void show(T t)
        {

        }
    }

}

ICustomerList<Animal> customerList=new CustomerList<Animal>();
ICustomerList<Animal> customerList1 = new CustomerList<Cat>();

泛型的协变和逆变更像是一种高级约束,是为规避

  1. 把子类做参数,却把父类当参数传入;
  2. 把子类做返回值,却在返回的时候,返回了一个父类。
posted @ 2023-03-24 13:32  飘雨的河  阅读(118)  评论(0编辑  收藏  举报