C# 泛型(Generic)简介

24 阅读6分钟

(1)泛型的引入

  1. 泛型--泛:宽泛的、不确定的; 型:类型---不确定的类型
  2. 泛型方法—做到了性能高—可以一个方法满足不同类型的需求

(2)泛型的声明

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

(3)泛型的特点和原理

在高级语言中,定义泛型T,在计算机执行的时候,一定要是一个具体类型 在底层如何支持?
在底层生成的结果是:List1[T] Dictinary2[Tkey,TValue],在底层生成了带有1、2、`3..... 编译器必须要能够支持泛型 CLR运行时环境也必须要支持泛型 泛型是框架的升级支持的,泛型不是语法糖 语法糖是编译器提供的便捷功能--.NetFramework2.0时代支持的

(4)泛型的应用

  1. 泛型方法---可以一个方法满足不同类型的需求

    泛型方法的定义如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public class CommonMethod
    {
        /// <summary>
        /// 泛型方法
        /// </summary>
        /// <typeparam name="T">类型变量 类型参数</typeparam>
        /// <param name="tParameter"></param>
        public static void Show<T>(T tParameter)
        {
            Console.WriteLine($"This is {typeof(CommonMethod).Name},parameterType={tParameter.GetType().Name},value={tParameter}");
        }
    }
}

泛型方法的调用

using MyGeneric;
try
{
    Console.WriteLine("今天知识点泛型!");
    int iValue = 123;
    string sValeu = "我是泛型";
    DateTime dtValue = DateTime.Now;
    object oValue = "234";
    
    //调用泛型方法
    CommonMethod.Show<int>(iValue); //如果可以通过参数推导出类型-- - 尖括号可以省略
    CommonMethod.Show<string>(sValeu);// 省略类型--语法糖
    CommonMethod.Show<DateTime>(dtValue);
    CommonMethod.Show<object>(oValue);
}
catch (Exception)
{

	throw;
}

  1. 泛型接口---可以一个接口满足不同类型的需求---尖括号+占位符 泛型接口的定义
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public interface GenericInterface<T>
    {
        public T Show();
    }
}

调用实例:

using MyGeneric.Extension;
using System;
using System.Collections.Generic;

namespace MyGeneric
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
               {
                  GenericInterfac<string> sgenericInterfac = null;
                  GenericInterfac<DateTime> dtgenericInterfac = null;
               }  
             }catch (Exception ex)
             {
                Console.WriteLine(ex.Message);
             }
        }
    }
}


4. 泛型类------可以一个类型满足不同类型的需求---尖括号+占位符

```js

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public abstract class GenericAbstractClass<T>
    {
        public void Show(T t){}
    }
    
    public class ChildClass1<S> : GenericInterfac<int> 
    { 
      
    }
}

调用实例:

using MyGeneric.Extension;
using System;
using System.Collections.Generic;

namespace MyGeneric
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
               {
                  GenericAbstractClass<string> sGenericClass = null;
                  GenericAbstractClass<DateTime> dtGenericClass = null;
                  GenericAbstractClass<int> iGenericClass = null;
                  //sGenericClass  dtGenericClass  iGenericClass 三者是什么关系?--一毛钱关系都没有
               }  
             }catch (Exception ex)
             {
                 Console.WriteLine(ex.Message);
             }
        }
    }
}
5. 泛型委托----可以一个委托满足不同类型的需求

```js
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyGeneric
{
    public delegate T Genericdelegate<T>();
}

调用实例:

using MyGeneric.Extension;
using System;
using System.Collections.Generic;

namespace MyGeneric
{
    class Program
    {
        static void Main(string[] args)
        {
            
            try
            {
               {
                   Genericdelegate<string> sGenericdelegate = null;
                   Genericdelegate<DateTime> dtGenericdelegate = null;
               }  
             }catch (Exception ex)
             {
                 Console.WriteLine(ex.Message);
             }
        }
    }
}

(5)泛型约束和泛型缓存

泛型约束

约束描述
where T :struct类型参数必须是不可为null的值类型。由于所有值类型都具有可访问的无参数构造函数,因此 struct约束表示new()约束,并且不能与new()约束结合使用。struct约束也不能与unmanaged约束结合使用。
where T :class类型参数必须是引用类型。此约束还应用于任何类、接口、委托或者数组类型。可为null的上下文中,T必须是不可为null的引用类型
where T :class?类型参数必须是可为 null 或不可为 null 的引用类型。 此约束还应用于任何类、接口、委托或数组类型。
where T :notnull类型参数必须是不可为 null 的类型。 参数可以是不可为 null 的引用类型,也可以是不可为 null 的值类型。
where T :default重写方法或提供显式接口实现时,如果需要指定不受约束的类型参数,此约束可解决歧义。 default 约束表示基方法,但不包含 classstruct 约束。 有关详细信息,请参阅default约束规范建议。
where T :new()类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。 new() 约束不能与 structunmanaged 约束结合使用。
where T :基类名称类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中,T 必须是从指定基类派生的不可为 null 的引用类型。
where T : <基类名>?类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中,T 可以是从指定基类派生的可为 null 或不可为 null 的类型。
where T : <接口名称>类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在的可为 null 的上下文中,T 必须是实现指定接口的不可为 null 的类型。
where T : <接口名称>?类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在可为 null 的上下文中,T 可以是可为 null 的引用类型、不可为 null 的引用类型或值类型。 T 不能是可为 null 的值类型。
where T : UT 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 在可为 null 的上下文中,如果 U 是不可为 null 的引用类型,T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型,则 T 可以是可为 null 的引用类型,也可以是不可为 null 的引用类型。
where T : unmanaged类型参数必须是不可为 null 的非托管类型unmanaged 约束表示 struct 约束,且不能与 struct 约束或 new() 约束结合使用。
  1. 基类约束

把类型参数当作People

调用的时候只能传递people或者people的子类型

泛型约束,要么不让你进来,如果让你进来就一定是没问题

public static void ShowBase<T>(T tParameter) where T : People
{
}
  1. 接口约束

把这个T当作ISports

只能传递ISports这个接口或者是实现这个接口的类

通过接口约束可以增加功能

public static void ShowInterface<T>(T tParameter) where T :ISport
  1. 引用类型约束

只能传递引用类型进来

public static void ShowClass<T>(T tParameter) where T : class
{
    //T t = new T(); //因为T 可能没有无参数构造构造函数
}
  1. 值类型约束

只能传递值类型进来

由于所有值类型都具有可访问的无参数构造函数,因此struct约束表示new()约束,并且不能与new()约束结合使用

public static void ShowStruct<T>(T tParameter) where T : struct
{
    T t = new T();
}
  1. 无参构造函数约束

必须有一个无参数构造函数才能当做参数传入

public static void ShowNew<T>(T tParameter) where T : new()
{
    T t = new T();
}
  1. 枚举类型约束

必须是一个枚举类型才能传入

public static void ShowEnum<T>(T tParameter) where T : Enum
{
             
}

(6)协变和逆变

是一种高级约束

是为了规避下面两种情况

1.把子类做参数,却把父类当参数传入;

2.把子类做返回值,却返回的时候,返回了一个父类

  • -协变 out:修饰类型参数;就可以让右边用子类,能让左边用父类

  • -逆变in:修饰类型参数;就可以让右边用父类;左边用子类

  • 协变和逆变只针对泛型接口和泛型委托