Net从2.0版 开始支持泛型。泛型允许在编译时实现类型安全。允许创建一个数据结构而不限于特定的数据类型。当使用该数据结构时,编译器保证它使用的类型与类型安全是相一致的。泛型提供了类型安全,但是没有造成任何性能损失和代码臃肿。泛型不仅限于类,还可用于接口、方法、委托…。
为什么使用泛型
- 类型安全
- 避免装箱拆箱带来的性能损耗
二进制代码重用
ArrayList arrs = new ArrayList
{
"wang",
100
};
这里如果将
item
设置为int
类型遍历集合,会抛出异常,因为集合中存在string
类型项foreach (var item in arrs)
{
Console.WriteLine(item);
}
遍历时不可避免装箱/拆箱操作
foreach (var item in arrs)
{
Console.WriteLine(item);
}
非泛型集合
var arr1 = new ArrayList();
arr1.Add(10);//Add方法参数值是object类型,这里将int->object
arr1.Add("wang");//Add方法参数值是object类型,这里将int->object
foreach (int item in arr1)//这里如果用int类型遍历集合,就挂掉了
{
Console.WriteLine(item);//读取时要进行拆箱
}
泛型集合
/*泛型集合在使用时定义好类型,之后就不存在装箱拆箱;
* 因为已经定义当前集合只能存入int类型,也就不能存入其他类型
* 编译时就会报错,错误应及早发现
*/
var arr2 = new List<int>();
arr2.Add(10);
foreach (var item in arr2)
{
Console.WriteLine(item);
}
通过参数化类型来实现在同一份代码上操作多种数据类型,利用“参数化类型”将类型抽象化,从而实现灵活的复用。每个集合的详细规范可以在
System.Collection.Generic
名称空间下找到。泛型方法
public class Print
{
public void PrintType<Tentity>() where Tentity : new()
{
Tentity t = new Tentity();
Console.WriteLine(t.GetType());
}
}
泛型类
public class MyArray<T> where T : new()
{
private readonly T[] array;
public MyArray(int size)
{
array = new T[size + 1];
}
public T GetItem(int index)
{
return array[index];
}
public void SetItem(int index, T value)
{
array[index] = value;
}
}
default
default
用于将泛型类型初始化为null
或0
。T t = default(T);
泛型接口
public interface IPrint<T>
{
void Add(T t);
}
泛型委托
public delegate void Print<T>(T t);
泛型在声明的时候可以不指定具体的类型,但是在使用的时候必须指定具体类型
Print<int> iPrint = new Print<int>();
这里的类型是int,如果存在继承并且子类也是泛型的,那么继承的时候可以不指定具体类型 ```csharp class Print
{ public T t; }
//这里继承时没有指定泛型类型
class ConsolePrint
}
同上,类实现泛型接口也是如此。
<a name="8f3b5c3e"></a>
## 泛型约束
所谓的泛型约束,就是约束类型 `T` ,使 `T` 必须遵循一定的规则。比如 `T` 必须继承自某个类,或者 `T` 必须实现某个接口等。
| **类型** | **描述** |
| --- | :--- |
| T:calss | 类型参数必须是引用类型,包括任何类、接口、委托或数组类型 |
| T:stauct | 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型 |
| T:new() | 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定 |
| T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类 |
| T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的 |
| T1:T2 | 类型T1派生自泛型类型T2,该约束也被称为裸类型约束 |
```csharp
class Person
{
public static void PintSayHello()
{
}
}
interface ISport
{
void Sport();
}
/// <summary>
/// 约束T必须是Person类型或者是Person的子类并且实现了ISport接口且T类型中必须有无参构造函数
/// </summary>
/// <typeparam name="T"></typeparam>
class Show<T> where T : Person, ISport, new()
{
}
/// <summary>
/// 约束T必须是引用类型并且实现了ISport接口且T类型中必须有无参构造函数
/// </summary>
/// <typeparam name="T"></typeparam>
class Show2<T> where T : class, ISport, new()
{
}
/// <summary>
/// 约束T必须是值类型并且实现了ISport接口
/// </summary>
/// <typeparam name="T"></typeparam>
class Show3<T> where T : struct, ISport
{
}
注意:泛型约束可以同时约束多个,有多个泛型约束时,
new()
约束一定是在最后
泛型协变与逆变
协变和逆变在.NET 4.0的时候出现,只能放在接口或者委托的泛型参数前。
out
协变covariant
,用来修饰返回值in
:逆变contravariant
,用来修饰传入参数
在C#中声明泛型接口时,可以使用 in
和 out
参数来控制这个泛型是协变还是逆变的(逆变有时也被翻译成抗变)协变和逆变是用来描述如果泛型存在继承关系时,两个泛型类是否能够直接赋值的问题。比如派生泛型 IInterface<Child>
是否能被赋值给 IInterface<Parent>
。
协变:只能是方法参数
public interface ICustomerListIn<in T>
{
void Show(T t);
}
/// <summary>
/// 泛型协变(子类到父类的转换)
/// </summary>
/// <remarks>
/// out:协变covariant,用来修饰返回值
/// </remarks>
[TestMethod]
public void GenericityCovariant()
{
// 直接声明Animal类
Animal animal = new Animal();
Console.WriteLine($"{animal.GetType()}");
// 直接声明Cat类
Cat cat = new Cat();
Console.WriteLine($"{cat.GetType()}");
// 声明子类对象指向父类
Animal animal2 = new Cat();
Console.WriteLine($"{animal2.GetType()}");
// 声明Animal类的集合
List<Animal> listAnimal = new List<Animal>();
foreach (var item in listAnimal)
{
Console.WriteLine($"{item.GetType()}");
}
// 声明Cat类的集合
List<Cat> listCat = new List<Cat>();
foreach (var item in listCat)
{
Console.WriteLine($"{item.GetType()}");
}
#if debug
// 一只Cat属于Animal,一群Cat也应该属于Animal。但实际上这样声明是错误的:因为List<Cat>和List<Animal>之间没有父子关系
List<Animal> list = new List<Cat>();
#endif
// 泛型协变,IEnumerable泛型参数类型使用了out修饰
ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
Console.WriteLine($"{customerList1.GetType()}");
ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();
Console.WriteLine($"{customerList2.GetType()}");
Assert.IsTrue(true);
}
逆变:只能是返回值
public interface ICustomerListIn<in T>
{
void Show(T t);
}
/// <summary>
/// 泛型逆变(父类到子类的转换)
/// </summary>
/// <remarks>
/// int:协变contravariant,用来修饰返回值(子类到父类的转换)
/// </remarks>
[TestMethod]
public void GenericityContravariant()
{
// 直接声明Animal类
Animal animal = new Animal();
Console.WriteLine($"{animal.GetType()}");
// 直接声明Cat类
Cat cat = new Cat();
Console.WriteLine($"{cat.GetType()}");
// 声明子类对象指向父类
Animal animal2 = new Cat();
Console.WriteLine($"{animal2.GetType()}");
// 声明Animal类的集合
List<Animal> listAnimal = new List<Animal>();
foreach (var item in listAnimal)
{
Console.WriteLine($"{item.GetType()}");
}
// 声明Cat类的集合
List<Cat> listCat = new List<Cat>();
foreach (var item in listCat)
{
Console.WriteLine($"{item.GetType()}");
}
ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>();
Console.WriteLine($"{customerListCat1.GetType()}");
ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();
Console.WriteLine($"{customerListCat2.GetType()}");
Assert.IsTrue(true);
}
高性能泛型缓存
静态字典缓存和常用的泛型缓存的性能相比,泛型缓存性能是非常优异的。泛型缓存是 JIT
产生全新的类,内存直接分配,由CPU查找内存地址。静态字典缓存需要根据地址去寻址查找。
/// <summary>
/// 类中的静态类型无论实例化多少次,在内存中只会有一个
/// 静态构造函数只会执行一次
/// 在泛型类中,T类型不同
/// 每个不同的T类型,都会产生一个不同的副本,所以会产生不同的静态属性、不同的静态构造函数
/// </summary>
/// <typeparam name="T"></typeparam>
gma warning disable S1118 // Utility classes should not have public constructors
public class GenericCache<T>
gma warning restore S1118 // Utility classes should not have public constructors
{
gma warning disable S3963 // "static" fields should be initialized inline
static GenericCache()
{
_CachedValue = string.Format("{0}_{1}",
typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
}
gma warning restore S3963 // "static" fields should be initialized inline
gma warning disable S2743 // Static fields should not be used in generic types
private static readonly string _CachedValue = "";
gma warning restore S2743 // Static fields should not be used in generic types
public static string GetCache()
{
return _CachedValue;
}
}
/// <summary>
/// 泛型会为不同的类型都创建一个副本
/// 所以静态构造函数会执行4次,而且每次静态属性的值都是一样的
/// 利用泛型的这一特性,可以实现缓存
/// 注意:只能为不同的类型缓存一次。泛型缓存比字典缓存效率高。泛型缓存不能主动释放
/// </summary>
[TestMethod]
public void GenericityCache()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(GenericCache<int>.GetCache());
Console.WriteLine(GenericCache<long>.GetCache());
Console.WriteLine(GenericCache<DateTime>.GetCache());
Console.WriteLine(GenericCache<string>.GetCache());
}
Assert.IsTrue(true);
}
参考
https://www.cnblogs.com/dotnet261010/p/9034594.html
https://www.cnblogs.com/yilezhu/p/10029782.html
https://www.cnblogs.com/aehyok/p/3384637.html