Bootstrap

C#中的泛型(约束、逆变、协变)

在 C# 中,泛型是一项强大的特性,它允许你创建通用的类、接口、方法、委托等,这些通用的结构可以处理多种不同的数据类型,而无需为每种类型编写重复的代码。

目录

1.泛型

什么是泛型?为什么要用泛型?泛型有什么优点?

泛型如何定义?

泛型运行原理?

泛型如何调用?

泛型的约束:

为什么要使用泛型约束?好处是什么?

怎么给泛型指定约束?

五种常用的泛型约束

泛型的协变和逆变:

协变和逆变概念:

注意事项

1.泛型

什么是泛型?为什么要用泛型?泛型有什么优点?

泛型(generic)是专门是为了处理多端代码不同数据类型上执行相同的指令而设计。

优点:

a.速度快且支持不同类型

b.提高代码复用性

c.性能高

d.语法优美简洁

e.保护类型的安全。

泛型如何定义?

泛型定义格式:<T>或<T,K,......> 其中T,K指未知类型。  T,K,M,N

泛型定义时,是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。

#泛型类 也可有多个未知的数据类型,中间用逗号隔开
public class 类名<T>(){
    代码块
}
#泛型方法
public static void 方法名<T>(){
    代码块
}

泛型运行原理?

程序在执行时,会进行编译,生成二进制代码,之后才被执行。

在使用泛型以后,编译到泛型时,会先生成占位符,之后会把占位符替换成具体的数据类型。

泛型如何调用?

//定义泛型方法
 public static void Show2<T>(T a)
 {
     Console.WriteLine($"参数类型:{a.GetType().FullName},参数值:{a}");
 }

// 调用,传递实参时,才给T指明具体类型。
Show2<int>(10);

泛型的约束:

为什么要使用泛型约束?好处是什么?

泛型的目的,相同的业务逻辑,支持不同的类型,如果一味的滥用,对代码安全性不利。

引入泛型约束主要控制泛型支持的类型在有限的范围内。

泛型约束的好处: 让代码异常出现在编译期间,而非运行期间,从而增强代码的安全性。

所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。

怎么给泛型指定约束?

只需要where关键字,加上约束的条件。如:where T: class    、     where T: struct

五种常用的泛型约束

where T :struct:约束类型参数必须是值类型,如整数、枚举等。

// where T : struct:要求类型参数必须是值类型,如整数、枚举等。

public class ValueTypeContainer<T> where T : struct
{
    public T Value { get; set; }

    public ValueTypeContainer(T value)
    {
        Value = value;
    }
}

class Program
{
    static void Main()
    {
        // 可以使用 int 这种值类型
        ValueTypeContainer<int> intContainer = new ValueTypeContainer<int>(10);
        // 下面这行代码会编译错误,因为 string 是引用类型
        // ValueTypeContainer<string> stringContainer = new ValueTypeContainer<string>("test"); 
    }
}0);

where T :class:约束类型参数必须是引用类型,像类、接口、委托等。

// where T :class  规定类型参数必须是引用类型,像类、接口、委托等。

public class ReferenceTypeContainer<T> where T : class
{
    public T Value { get; set; }

    public ReferenceTypeContainer(T value)
    {
        Value = value;
    }
}

class MyClass { }

class Program
{
    static void Main()
    {
        MyClass obj = new MyClass();
        // 可以使用引用类型 MyClass
        ReferenceTypeContainer<MyClass> refContainer = new ReferenceTypeContainer<MyClass>(obj);
        // 下面这行代码会编译错误,因为 int 是值类型
        // ReferenceTypeContainer<int> intContainer = new ReferenceTypeContainer<int>(10); 
    }
}

where T :new():约束要求类型参数必须有一个无参数的公共构造函数,这样就能在泛型类中使用new()创建实例。

//where T : new():要求类型参数必须有一个无参数的公共构造函数,
//这样就能在泛型类中使用 new T() 创建实例。

public class CreatableContainer<T> where T : new()
{
    public T CreateInstance()
    {
        return new T();
    }
}

class DefaultConstructibleClass
{
    public DefaultConstructibleClass() { }
}

class Program
{
    static void Main()
    {
        CreatableContainer<DefaultConstructibleClass> creator = new CreatableContainer<DefaultConstructibleClass>();
        DefaultConstructibleClass instance = creator.CreateInstance();
    }
}

where T :基类名:表示类型参数必须是指定基类或其派生类。

//where T : 基类名:表示类型参数必须是指定基类或其派生类。

public class Animal { }
public class Dog : Animal { }

public class AnimalProcessor<T> where T : Animal
{
    public void Process(T animal)
    {
        // 处理动物的逻辑
    }
}

class Program
{
    static void Main()
    {
        Dog dog = new Dog();
        AnimalProcessor<Dog> processor = new AnimalProcessor<Dog>();
        processor.Process(dog);
        // 可以使用基类 Animal 本身
        Animal animal = new Animal();
        AnimalProcessor<Animal> animalProcessor = new AnimalProcessor<Animal>();
        animalProcessor.Process(animal);
    }
}

where T :​​​​​​​接口名:要求类型参数必须实现指定的接口。

    //where T : 接口名:要求类型参数必须实现指定的接口。
    
    public interface IMyInterface
    {
        void DoSomething();
    }
    
    public class ImplementingClass : IMyInterface
    {
        public void DoSomething()
        {
            System.Console.WriteLine("Doing something...");
        }
    }
    
    public class InterfaceProcessor<T> where T : IMyInterface
    {
        public void CallMethod(T obj)
        {
            obj.DoSomething();
        }
    }
    
    class Program
    {
        static void Main()
        {
            ImplementingClass impl = new ImplementingClass();
            InterfaceProcessor<ImplementingClass> interfaceProcessor = new InterfaceProcessor<ImplementingClass>();
            interfaceProcessor.CallMethod(impl);
        }
    }

    泛型的协变和逆变:

    协变和逆变概念:

    协变(Covariance):协变允许使用比泛型类型参数指定的类型派生程度更大(更具体的子类型)的类型。使用out关键字来控制返回值类型。 

    简单来说,就是可以将一个派生类的泛型对象隐式转换为基类的泛型对象。在 C# 中,协变只适用于引用类型,并且只能用于泛型接口和委托的输出位置(如返回类型)。

    逆变(Contravariance):逆变允许使用比泛型类型参数指定的类型派生程度更小(更宽泛的基类型)的类型。使用in关键字来控制参数的类型。

    即可以将一个基类的泛型对象隐式转换为派生类的泛型对象。逆变同样只适用于引用类型,并且只能用于泛型接口和委托的输入位置(如方法参数)。

    协变(Contravariance)示例:

    using System;
    
    // 定义一个协变的泛型接口
    public interface IMyCovariantInterface<out T>
    {
        T GetValue();
    }
    
    // 实现协变接口的类
    public class MyClass<T> : IMyCovariantInterface<T>
    {
        private T value;
        public MyClass(T value)
        {
            this.value = value;
        }
    
        public T GetValue()
        {
            return value;
        }
    }
    
    class Program
    {
        static void Main()
        {
            // 创建一个具体类型的对象
            IMyCovariantInterface<string> stringInterface = new MyClass<string>("Hello");
            // 进行协变转换
            IMyCovariantInterface<object> objectInterface = stringInterface;
    
            // 调用方法
            object result = objectInterface.GetValue();
            Console.WriteLine(result);
        }
    }

    在上述代码中,IMyCovariantInterface<out T>是一个协变的泛型接口,使用out关键字声明T为协变类型参数。MyClass<T>实现了该接口。在Main方法中,我们创建了一个IMyCovariantInterface<string>类型的对象,然后将其转换为IMyCovariantInterface<object>类型,因为stringobject的子类型,这种转换是安全的,这就是协变的体现。

    逆变(Contravariance)示例:

    using System;
    
    // 定义一个逆变的泛型接口
    public interface IMyContravariantInterface<in T>
    {
        void SetValue(T value);
    }
    
    // 实现逆变接口的类
    public class MyAnotherClass<T> : IMyContravariantInterface<T>
    {
        private T value;
        public void SetValue(T value)
        {
            this.value = value;
        }
    }
    
    class Program
    {
        static void Main()
        {
            // 创建一个具体类型的对象
            IMyContravariantInterface<object> objectInterface = new MyAnotherClass<object>();
            // 进行逆变转换
            IMyContravariantInterface<string> stringInterface = objectInterface;
    
            // 调用方法
            stringInterface.SetValue("World");
        }
    }

    在这个例子中,IMyContravariantInterface<in T>是一个逆变的泛型接口,使用in关键字声明T为逆变类型参数。MyAnotherClass<T>实现了该接口。在Main方法中,我们创建了一个IMyContravariantInterface<object>类型的对象,然后将其转换为IMyContravariantInterface<string>类型,因为objectstring的基类型,这种转换是安全的,这就是逆变的体现。

    注意事项

    只适用于引用类型:协变和逆变只适用于引用类型,不能用于值类型(如intdouble等)。

    类型参数使用位置限制:协变类型参数只能用于输出位置(如返回类型),逆变类型参数只能用于输入位置(如方法参数)。如果违反这个规则,编译器会报错。

    支持范围:协变和逆变主要应用于泛型接口和委托,类和结构体不支持协变和逆变。

    ;