泛型准确的来说是通用类型,通常作为一个方法模板和类型的模板,泛型类和泛型方法兼具可重用性、类型安全性和效率,这是非泛型类和非泛型方法无法实现的。
泛型表示:List<T>,T是一个占位符,我们在Dynamic介绍过动态类型一般在执行的时候知道他的数据类型,那么T在编译的时候他就确定了它的类型。
命名空间 :System.Collections.Generic
我们经常在一个项目框架中泛型应该是用得最多了,那么它能给我们带来那些有利的功能呢?
泛型优点:
1.在泛型未出来之前,实现通用类型一般都是用object基类型来做通用类型强制转换,所以集合每次操作的时候都会拆箱装箱操作,而泛型就很好的解决了这个问题。
2.由于每次操作集合的时候都需要把数据转换成object类型,在编译之前我们是检测不到数据的类型,因为都是object基类型,所以在编译的时候我们无法预测到这个类型带来的风险
关于泛型使用的范围:
泛型类
class Student<T>{}
class Teacher<T,U>{}
泛型接口
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
泛型方法
void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }
泛型类型
List<int> list = new List<int>() { 1, 2, 3 };
List<string> listStr = new List<int>() { "zhangsan", "lisi", "wangmazi" };
泛型委托
public delegate void Del<T>(T item);
泛型参数的约束:
约束 | 说明 |
where T : struct | 类型参数必须是值类型。 可以指定除 Nullable<T> 以外的任何值类型。 |
where T : class | 类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型 |
where T : unmanaged | 类型参数不能是引用类型,并且任何嵌套级别均不能包含任何引用类型成员。 |
where T : new() | 类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定 |
where T : <基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
where T : <接口名称> | 类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 |
where T : U | 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 |
注意:某些约束是互斥的。 所有值类型必须具有可访问的无参数构造函数。 struct
约束包含 new()
约束,且 new()
约束不能与 struct
约束结合使用。 unmanaged
约束包含 struct
约束。 unmanaged
约束不能与 struct
或 new()
约束结合使用。
约束多个参数:
class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }
未绑定的类型参数:
没有约束的类型参数(如公共类 SampleClass<T>{} 中的 T)称为未绑定的类型参数。 未绑定的类型参数具有以下规则:
不能使用 != 和 == 运算符,因为无法保证具体的类型参数能支持这些运算符。
可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。
可以将它们与 null 进行比较。 将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始终返回 false。
类型参数作为约束:
public class List<T>
{
public void Add<U>(List<U> items) where U : T
{}
}
非托管约束:
从 C# 7.3 开始,可使用 unmanaged 约束来指定类型参数必须为“非托管类型”。 “非托管类型”不是引用类型,且任何嵌套级别都不包含引用类型字段。 通过 unmanaged 约束,用户能编写可重用例程,从而使用可作为内存块操作的类型,如以下示例所示:
unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
var size = sizeof(T);
var result = new Byte[size];
Byte* p = (byte*)&argument;
for (var i = 0; i < size; i++)
result[i] = *p++;
return result;
}
委托约束:
同样从 C# 7.3 开始,可将 System.Delegate 或 System.MulticastDelegate 用作基类约束。 CLR 始终允许此约束,但 C# 语言不允许。 使用 System.Delegate 约束,用户能够以类型安全的方式编写使用委托的代码。
Action first = () => Console.WriteLine("this");
Action second = () => Console.WriteLine("that");
var combined = first.TypeSafeCombine(second);
combined();
Func<bool> test = () => true;
//必须是相同委托签名类型
//var badCombined = first.TypeSafeCombine(test);
枚举约束:
从 C# 7.3 开始,还可指定 System.Enum 类型作为基类约束。 CLR 始终允许此约束,但 C# 语言不允许。 使用 System.Enum 的泛型提供类型安全的编程,缓存使用 System.Enum 中静态方法的结果
enum Rainbow
{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}
var map = EnumNamedValues<Rainbow>();
泛型可变性:
协变性:协变性指的是——泛型类型参数可以从一个派生类隐式转化为基类。用out修饰,方法只能返回这个T类型
IEnumerable<string>strings=new List<string>();
逆变性:逆变性指的是——泛型类型参数可以从一个基类隐式转化为派生类,用in修饰,方法只能传入一个T类型
并不是所有类型都支持泛型的协变和逆变的,下面列出泛型的协变和你逆变中值得注意和明 确的地方:
1. 只有接口和委托支持协变和逆变(如Func<out TResult>,Action<in T>),类或泛型方 法的类型参数都不支持协变和逆变。
2. 协变和逆变只适用于引用类型,值类型不支持协变和逆变(因为可变性存在一个引用转换, 而值类型变量存储的就是对象本身,而不是对象的引用),所以List<int>无法转化为 Ienumerable<object>.
3. 必须显示用in或out来标记类型参数。
4. 委托的可变性不要再多播委托中使用,相信这点很多人都没有注意到的, 下面我举个例子 来说明下,当大家遇到这样的问题可以知道为什么:
上面代码可以通过编译,因为泛型Func<out T>支持协变,所以将Func<string>转换为 Func<object>类型,但是对象本身仍然为Func<string>类型,然而Delegate.Combine方 法要求参数必须为相同类型——否则该方法无法确定要创建什么类型的委托(是 Func<string>类型呢还是Func<object>?),所以上面代码在运行时会抛出 ArgumetException(错误信息为——委托必须具有相同的类型)。我们可以稍微修改下上 面代码来使其不出现运行时错误