C#中关于泛型约束、协变和逆变!

C# · 2024-02-25 · 1788 人浏览
C#中关于泛型约束、协变和逆变!

泛型01.png

1、泛型约束文档(来源于网络)

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

2、泛型约束举例(自我总结)

1、结构约束

public class StructList<T> where T: struct
{
    // do something...
}

// CS0453:类型”string“必须是不可为null值的类型,才能用作泛型类型或方法”StructLiss<T>"中的参数“T”
StructList <String> list = new StructList<String>();

// 正确示例
SturctList <int> list = new StructList<int>();

2、引用类型约束

public class ClassList<T> where T:class
{
    // do something...
}

// 错误示例:CS0452:类型"int"必须是应用类型才能用作泛型类或方法"ClassList<T>"中的参数“T”
ClassList<int> list = new ClassList<int>();

// 正确示例
ClassList<MyClass> list = new ClassList<MyClass>();
ClassList<Student> Stu = new CLassList<Student>();

3、基类名约束

// 基类名约束:必须包含此类才可以使用(下例:必须包含Student类才可以使用BaseClassList类)
// 语法:where T:基类
public class BaseClassList<T> where T: Student
{
    // do something...
}

// 需要使用到的类
public class Student
{
    public string Name{set;get;}
}
public class chusan:Student
{
    public void Entertainment()
    {
        Console.WriteLine("Hello!");
    }
}
public class Age:chusan
{
    public string Age{set;get;}
}
public class Jp
{}

// 正确使用约束
BaseClassList<Student>  stu = new BaseClassList<Student>();
BaseClassList<chusan> stu = new BaseClassList<chusan>();
BaseClassList<Age> age = new BaseClassList<Age>();

// 错误使用约束(没有从“A”到“B”的隐式转换)
BaseClassList<int> age = new BaseClassList<int>();
BaseClassList<Jp> age = new BaseClassList<Jp>();

4、接口约束

// 语法:where T:接口名
public class InterfaceList<T> where T:Iwork
{
    // do something...
}

// 示例类
public class Human
{
    // do something...
}
public interface Iwork
{
    // do something...
}
public class Chinese:Human,Iwork
{
    // do something...
}
public class Japanese:Iwork
{
    // do something...
}

// 约束示例
// <正确>
InterfaceList<Chinese> list = new InterfaceList<Chinese>();
InterfaceList<Japanese> list = new InterfaceList<Japanese>();
// <错误>
// 原因:因为Human类没有继承接口Iwork,所以不能使用该泛型类
InterfaceList<Human> list = new InterfaceList<Human>();

5、构造函数约束

🚀:使用“无参的公共构造函数”进行约束。
🏷:类在声明时,若类中没有定义构造函数,则就默认有一个无参公共构造函数;所以实例化时,会自动生成一个默认的无参构造函数。

// 语法:where T:new()
public class NoparamList<T> where T:new()
{
    // do something...
}

// 示例类:
public class Chinese
{
    // do something...
}
public class Japanese
{
    public Japanese(string Jp)
    {
        Console.WriteLine("小日子过得不错的人!");
    }
}

// 约束示例
// <正确>
NoparamList<Chinese> list = new NoparamList<Chinese>();
// <错误>(因为带了有参构造函数)
NoparamList<Japanese> list = new NoparamList<Japanese>();

6、泛型约束泛型

public class Test<U>
{
    public void Add<T>(T item) where T:U
    {
        // do something...
    }
}

// 代码示例
// Chinese继承Human,anhui继承Chinese,Japanese没有继承任何类
Test <Human> list = new Test<Human>();
list.Add(new Chinese());    // 合法
list.Add(new anhui());        // 合法
list.Add(new Japanese());    // 不合法

3、协变和逆变

🚀:前提:只能用在接口或委托上

Chinese chinese = new Chinese();
Human human = chinese;
Human human2 = new Chinese();

1、协变

🚀:如果存在一个泛型接口Interface ,它的泛型参数子类类型interface<子类类型>可以安全的转换为泛型父类类型interface<父类类型>,此过程就是协变。
🔖:协变和逆变的出现,就是为了解决泛型在类型继承中的类型转换不安全的问题。
🔖:调用时,只能调用返回参数为T的方法

public interface ISpeak<out T>
{
    public T SpeakLanguage();
}

public class Speak<T>:ISpeak<T>
{
    public T SpeakLanguage()
    {
        return default(T);
    }
}

// 协变写法
ISpeak<基类> Speak = new Speak<派生类>();

2、逆变

🚀:如果存在一个泛型接口Ibar,它的泛型参数父类类型Ibar<父类>可以安全的转换为泛型子类类型Ibar<子类>,此过程为逆变。
🔖:不能使用泛型类型参数作为这个方法的返回值
🔖:调用时,只能调用返回参数为T的方法

public interface ISpeak<in T>
{
    public void SpeakLanguage(T t);
}

public class Speak<T>:ISpeak<T>
{
    public void SpeakLanguage(T t)
    {
        // do something...
    }
}

// 逆变写法
ISpeak<派生类> Speak = new Speak<基类>();
C# 泛型