在 C# 中,泛型(Generics)是一种强大的特性,它允许我们编写可重用的代码,不仅能够在编译时类型安全地处理不同类型的数据,还可以提高代码的性能和可读性。泛型在集合类、算法和数据结构等方面得到广泛应用。 C# 中的泛型通过在定义类、接口、方法等时使用类型参数来实现。类型参数可以是任何有效的类型,包括类、结构、接口、委托和基元类型。使用泛型的主要目的是在编写代码时不指定具体的类型,而是在使用时由调用方指定类型。
public class GenericClass<T>
{
private T genericField;
public GenericClass(T value)
{
genericField = value;
}
public T GenericMethod(T parameter)
{
Console.WriteLine($"Generic field: {genericField}, Parameter: {parameter}");
return parameter;
}
}
// 使用泛型类
GenericClass<int> intGenericClass = new GenericClass<int>(10);
int result = intGenericClass.GenericMethod(5); // 输出: Generic field: 10, Parameter: 5
GenericClass<string> stringGenericClass = new GenericClass<string>("Hello");
string result = stringGenericClass.GenericMethod("World"); // 输出: Generic field: Hello, Parameter: World
在上述示例中,GenericClass<T> 是一个泛型类,它接受一个类型参数 T。通过在实例化时指定类型参数,我们可以创建具有不同类型的对象,并在方法中使用这些类型。
泛型约束(Generic Constraints)允许开发人员对泛型类型参数进行限制,以确保类型参数满足特定的条件。这些约束可以应用于泛型类、泛型方法和泛型委托,以提供更多的类型安全性和灵活性。
该约束要求类型参数 T 必须是引用类型(类、接口、委托、数组或委托)。这意味着泛型类型参数不能是值类型(如 int、double 等)。
public class MyClass<T> where T : class
{
// 泛型类定义
}
该约束要求类型参数 T 必须是值类型(如 int、double、struct 等)。这意味着泛型类型参数不能是引用类型。
该约束要求类型参数 T 必须具有无参数的公共构造函数。这允许在泛型代码中创建 T 类型的新实例。
public class MyClass<T> where T : new()
{
// 泛型类定义
}
该约束要求类型参数 T 必须是指定的基类或其派生类。
public class MyBaseClass
{
// 基类定义
}
public class MyDerivedClass<T> where T : MyBaseClass
{
// 泛型类定义
}
该约束要求类型参数 T 必须实现指定的接口。
public interface IMyInterface
{
// 接口定义
}
public class MyClass<T> where T : IMyInterface
{
// 泛型类定义
}
该约束要求类型参数 T 必须是类型参数 U 或其派生类。这允许在泛型代码中对类型参数进行比较和约束。
public class MyClass<T, U> where T : U
{
// 泛型类定义
}
可以同时使用多个约束来限制类型参数的行为。多个约束之间使用逗号分隔。
public class MyClass<T> where T : class, IMyInterface, new()
{
// 泛型类定义
}
协变性(Covariance)和逆变性(Contravariance)是 C# 中泛型类型参数的特性,允许在一定条件下进行类型的隐式转换。 需要注意的是,协变性和逆变性只适用于引用类型,不适用于值类型(如基本数据类型)。此外,协变性和逆变性只在接口和委托中有效,不适用于类或结构。 协变性和逆变性的引入使得泛型类型参数更加灵活,可以在一定条件下进行类型的隐式转换,提供了更好的代码重用性和互操作性。然而,为了确保类型安全,协变性和逆变性受到一些限制,如只允许在特定的接口和委托中使用,并且只能在满足一定条件的情况下使用。
协变性允许将派生类型(子类型)作为基类型(父类型)使用。换句话说,协变性允许将泛型类型参数替换为其派生类型。在协变性中,可以将返回类型或只读属性的类型声明为派生类型。
interface IAnimal { }
class Cat : IAnimal { }
IEnumerable<Cat> cats = new List<Cat>();
IEnumerable<IAnimal> animals = cats; // 协变性,将派生类型 Cat 赋值给基类型 IAnimal
在上述示例中,IEnumerable<T> 是一个泛型接口,如果类型参数 T 是协变的,那么可以将 IEnumerable<Cat> 赋值给 IEnumerable<IAnimal>,因为 Cat 是 IAnimal 的派生类型。
逆变性允许将基类型作为派生类型使用。换句话说,逆变性允许将泛型类型参数替换为其基类型。逆变性通常用于接受参数的方法或可写属性。
interface IComparer<in T>
{
int Compare(T x, T y);
}
IComparer<IAnimal> animalComparer = new AnimalComparer();
IComparer<Cat> catComparer = animalComparer; // 逆变性,将基类型 IAnimal 赋值给派生类型 Cat
在上述示例中,IComparer<T> 是一个泛型接口,如果类型参数 T 是逆变的,那么可以将 IComparer<IAnimal> 赋值给 IComparer<Cat>,因为 IComparer<IAnimal> 可以比较 Cat 类型的对象。
C# 11引入了泛型特性(Generic Attributes)的概念,允许在编写自定义特性时使用泛型类型参数。泛型特性可以在运行时获取和使用类型参数的信息。
在这个功能提供之前,如果需要在特性(Attribute)中获得类型(Type)信息,需要实现类似下方代码:
// Before C# 11:
public class TypeAttribute : Attribute
{
public TypeAttribute(Type t) => ParamType = t;
public Type ParamType { get; }
}
并且为了应用该属性,需要使用 typeof 运算符:
[TypeAttribute(typeof(string))]
public string Method() => default;
使用此新功能,可以改为创建泛型属性:
// C# 11 feature:
public class GenericAttribute<T> : Attribute { }
然后指定类型参数以使用该属性:
[GenericAttribute<string>()]
public string Method() => default;
应用属性时,必须提供所有类型参数。 换句话说,泛型类型必须完全构造。
public class GenericType<T>
{
[GenericAttribute<T>()] // Not allowed! generic attributes must be fully constructed types.
public string Method() => default;
}