C#中泛型的使用

一、简介

简单记录一下C#中泛型类,泛型方法,泛型委托等泛型的使用,从.NetFramework2.0开始,支持泛型,通过反编译可以看到使用泛型的地方,都是使用了占位符 `1,当运行的时候,会将占位符替换成对应的类型

二、泛型方法

泛型方法就是为了解决不同类型要使用同样方法的问题,泛型方法需要在方法名的后面带一个<T>,T是类型参数,只是一个占位符,也可以用别的名字代替

2.1 泛型方法的声明

//实现string 转换为其他类型
public static T TypeConvert<T>(string val)
{
    if (String.IsNullOrWhiteSpace(val))
        return default(T);
    if (typeof(T).IsEnum)
         return (T)Enum.Parse(typeof(T), val, true);
    return (T)Convert.ChangeType(val, typeof(T));
}

2.2 泛型方法的调用

int num = TypeConvert<int>("1");//将字符l类型的1转换成int 类型的1

三、泛型类和泛型接口

3.1 泛型类的声明

泛型类就是一个类可以实现多个类型的需求

我们常用的集合List<T> 就是一个泛型类,可以转到定义看到其声明 public class List<T> 

当然我们进行实例化的时候,必须要指定类型,List<string> names = new List<string>(); 

3.2 泛型接口

泛型接口,就是一个接口 满足多个多个类型的需求

//声明泛型接口
public interface GenericInterface<T>
{
}

四、泛型委托

泛型委托与普通的委托相比,它可以适应更多的类型

微软官方提供了两种泛型委托,一个是Action,无返回值,另一个是Func,带返回值

我们可以自定义泛型委托,如下所示

public delegate void GenericDelegate <T>(T para);

五、泛型缓存

泛型缓存: 会根据传入的不同类型,分别生成不同的副本 。主要针对不同类型,每次都会调用都会产生与之对应的相同的数据的现象,可以使用泛型缓存,将第一次生成的数据存放在缓存中,再次调用时直接返回该数据。

5.1 泛型缓存声明

public class GenericCache<T>
{
  static GenericCache()
  {
    _Sql = GetSql(T);
  }

  private static string _Sql = "";

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

5.2 泛型缓存调用

//对于int 和 string 这两种类型,会生成不同的副本,生成的sql不一样,当第二次调用时,则不需要再重新生成sql,可直接返回第一次生成的sql,对于不同的类型,缓存的数据也都不同
GenericCache<int>.GetCache();
GenericCache<string>.GetCache();

六、泛型约束

泛型约束,顾名思义,就是传入的类型进行约束,防止任何类型都可以传进去导致出现异常

public static void GenericMethod<T>(T t)
        where T : class // 引用类型约束  就只能传入引用类型的参数
{
}

public static void GenericMethod1<T>(T t)
        where T : struct // 结构体类型   int float ...
{
}

public static void GenericMethod2<T>(T t)
        where T : new() // 无参数公共构造函数约束
{
}

public static void GenericMethod3<T>(T t)
        where T : P //基类约束,传入类型必须是P或P的派生类
{
}
public static void GenericMethod4<T>(T t)
        where T : IP //接口约束,传入类型必须实现接口IP
{
}

七、协变与逆变

7.1 协变跟逆变是对泛型类的继承关系的表述

在使用泛型的时候,会存在一些不和谐的地方,首先准备两个继承关系的类看一下

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

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

Cat继承于Animal,当使用集合List时,就会发现存在矛盾

Animal cat = new Cat();
List<Cat> cats = new List<Cat>();
List<Animal> animals = new List<Animal>();
List<Animal> animal_cats = new List<Cat>();//编译器报错

按理说,一只猫是一个动物,一群猫也应该是一群动物才对,这里创建时就会发现编译器会报错,原因就是List<Animal> 与 List<Cat>是两个类,不存在继承关系,在这里就需要使用到协变和逆变了

7.2 协变

使用IEnumerable实现

IEnumerable<Animal> cats = new List<Cat>();

也可自己定义模仿IEnumerable 定义接口实现,需要使用到out

public interface IMyListOut<out T>
{
    T Show();
}
public class MyListOut<T> : IMyListOut<T>
{
    public T Show()
    {
      return default;
    }
}
IMyListOut<Animal> myListOut = new MyListOut<Cat>();//使用

在使用协变时,需要使用out 修饰,并且左边只能传入基类,只能作为返回值而不能作为参数

7.3 逆变

逆变需要使用到关键字in

public interface IMyListIn<in T>
{
    void Show(T t);
}

public class MyListIn<T> : IMyListIn<T>
{
    public void Show(T t)
    {
         Console.WriteLine(t.GetType());
    }
}
IMyListIn<Cat> myListIn = new MyListIn<Animal>();

in用于修饰传入的子类,且只能为参数,不可为返回值

7.4 协变与逆变同时使用 

public interface IMyListOutAndIn<in Tin, out Tout>
{
    void ShowT(Tin t);
    Tout GetT();
    Tout Together(Tin t);
}
public class MyListOutAndIn<Tin, Tout> : IMyListOutAndIn<Tin, Tout>
{
    public Tout GetT()
    {
        return default;
    }

    public void ShowT(Tin t)
    {
        Console.WriteLine(t.GetType());
    }

    public Tout Together(Tin t)
    {
        Console.WriteLine(t.GetType());
        return default;
    }
}    
IMyListOutAndIn<Cat, Animal> myListOutAndIn = new MyListOutAndIn<Animal, Cat>();

 

posted @ 2022-09-12 15:05  just--like  阅读(1593)  评论(0编辑  收藏  举报