C# 系列 -- 泛型

344 阅读5分钟

泛型

泛型顾名思义就是允许我们编写一个可以与 任何数据类型 一起工作的类或方法

泛型类

什么是泛型类

泛型类是一种可以存储任何类型的类。泛型类使用尖括号 <T> 来表示类型参数。例如,您可以创建一个泛型类 MyClass<T>,如下所示:

public class MyClass<T>
{
    private T data;

    public MyClass(T value)
    {
        data = value;
    }

    public T GetData()
    {
        return data;
    }
}

我们可以使用任何类型创建 MyClass<T> 的实例,例如 MyClass<int>``MyClass<string> 或 MyClass<MyCustomType>

特点

  • 泛型类增加了可重用性。 类型参数越多意味着它变得越可重用。 但是,过多的泛化会使代码难以理解和维护
  • 泛型类可以是其他泛型或非泛型类或抽象类的基类
  • 泛型类可以从其他泛型或非泛型接口、类或抽象类派生

实践

单个泛型参数

先定义一个类:MyList

public class MyList<T> // 通过 T 把类型传进来
{
    private T[] list = new T[4]; // 当 T 是 int 的时这就相当于 int[] list = new int[]

    public T this[int index]
    {
        get => list[index];
        set => list[index] = value;
    }

    public T this[string index]
    {
        get
        {
            var i = Convert.ToInt32(index);
            return list[i];
        }
        set
        {
            var i = Convert.ToInt32(index);
            list[i] = value;
        }
    }
}

可以用作数组,也可以用作字符串数组:

class Program
{
    static void Main(string[] args)
    {
        var list1 = new MyList<int>(); 
        list1[0] = 1;
        list1[1] = 2;
        list1[2] = 3;
        list1[3] = 4;
        for (int i = 0; i < 4; i++)
        {
            Console.Write(list1[i.ToString()] + " ");
        }
        Console.Write('\n');
        var list2 = new MyList<string>();
        // 可以把 int 改成 string, 当然也可以是其它类型如 float decimal 等
        list2[0] = "a";
        list2[1] = "b";
        list2[2] = "c";
        list2[3] = "d";
        for (int i = 0; i < 4; i++)
        {
            Console.Write(list2[i.ToString()] + " ");
        }
    }
}

多个泛型参数

KeyValuePair 是 一个系统类。我们看到它使用了两个类型参数,并且它的类型参数名不在是T了,变成了 TKey,TValue。

class KeyValuePair<TKey, TValue>
{
    public TKey Key { get; set; }
    public TValue Value { get; set; }
}

再来看其它的一个例子, 在 Restful的Api当中,我们有时候会返回一个特定的格式。我们就可以把返回类型 定义在泛型类型参数里。

class Program
{
    static void Main(string[] args)
    {
        var vm = new ResultVm<DateTime>(DateTime.Now);
        Console.WriteLine(vm.PlayLoad);

    }
}

public class ResultVm<T1>
{
    public ResultVm(T1 playLoad) //构造器这边不需要<>这个东西
    {
        this.PlayLoad = playLoad;
    }

    public T1 PlayLoad { get; set; }

    public List<string> ErrorMessages { get; set; }
}

泛型方法

什么是泛型方法

泛型方法是一种可以使用任何类型的参数的方法。泛型方法使用尖括号 <T> 来表示类型参数。例如,您可以创建一个泛型方法 Swap<T>,如下所示:

public static void Swap<T>(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}

您可以使用任何类型调用 Swap<T> 方法,例如 Swap<int>(ref a, ref b)Swap<string>(ref a, ref b) 或 Swap<MyCustomType>(ref a, ref b)

实践

先定义一个类:MyList

class MyList<T>
{
    private T[] _data = new T[10];
    
    public void AddOrUpdate(int index, T item)
    {
        if(index >= 0 && index < 10)
            _data[index] = item;
    }

    public T GetData(int index)
    {
        if(index >= 0 && index < 10)
            return _data[index];
        else 
            return default(T);
    }
}

上面的 AddorUpdate() 和 GetData() 方法是泛型方法

item 参数的实际数据类型将在实例化 DataStore 类时指定:

class Program
{
    static void Main(string[] args)
    {
        var names = new MyList<string>();
        names.AddOrUpdate(0, "Ma");
        names.AddOrUpdate(1, "Lema");
        names.AddOrUpdate(2, ".net");

        var empIds = new MyList<int>();
        empIds.AddOrUpdate(0, 50);
        empIds.AddOrUpdate(1, 65);
        empIds.AddOrUpdate(2, 89);
        var id = empIds.GetData(0);
    }
}

泛型参数类型可以与带或不带非泛型参数和返回类型的多个参数一起使用。 以下是有效的泛型方法重载

public void AddOrUpdate(int index, T data) { }
public void AddOrUpdate(T data1, T data2) { }
public void AddOrUpdate<U>(T data1, U data2) { }
public void AddOrUpdate(T data) { }

泛型接口

什么是泛型接口

泛型接口是一种可以使用任何类型的接口。泛型接口使用尖括号 <T> 来表示类型参数。例如,您可以创建一个泛型接口 IRepository<T>,如下所示:

public interface IRepository<T>
{
    void Add(T item);
    void Remove(T item);
    T Find(Func<T, bool> predicate);
}

您可以使用任何类型实现 IRepository<T> 接口,例如 IRepository<int>IRepository<string> 或 IRepository<MyCustomType>

泛型约束

什么是泛型约束

C# 允许我们使用约束来限制客户端代码在实例化泛型类型时 指定某些类型。 如果您尝试使用指定约束不允许的类型来实例化泛型类型,则会出现 编译时错误

可以使用泛型类型名称后的 where 子句对泛型类型指定 一个或多个 约束。

GenericTypeName<T> where T  : contraint1, constraint2

以下示例演示了在实例化泛型类时具有引用类型约束的泛型类

class ResultVm<T> where T : class
{
    public T Payload { get; set; }
}

一些约束

约束描述
where T : class指定泛型类型参数 T 必须是引用类型(如类、接口、委托或数组)
where T : struct指定泛型类型参数 T 必须是值类型(如结构体或枚举)
where T : new()指定泛型类型参数 T 必须具有无参数的公共构造函数
where T : base-class-name指定泛型类型参数 T 必须从指定的基类(如 MyBaseClass)继承
where T : interface-name指定泛型类型参数 T 必须实现指定的接口(如 IMyInterface

实践

public class MyClass<T> where T : class, new()
{
    private T data;

    public MyClass()
    {
        data = new T(); // 使用无参数的公共构造函数创建 T 类型的实例
    }

    public void SetData(T value)
    {
        data = value;
    }

    public T GetData()
    {
        return data;
    }
}

在上面的示例中,我们定义了一个泛型类 MyClass<T>,并为泛型类型参数 T 添加了两个约束:T 必须是引用类型,并且具有无参数的公共构造函数。这样,我们可以在 MyClass<T> 类中使用 new T() 创建 T 类型的实例。