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->objectarr1.Add("wang");//Add方法参数值是object类型,这里将int->objectforeach (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,该约束也被称为裸类型约束 |```csharpclass 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 constructorspublic class GenericCache<T>gma warning restore S1118 // Utility classes should not have public constructors{gma warning disable S3963 // "static" fields should be initialized inlinestatic GenericCache(){_CachedValue = string.Format("{0}_{1}",typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));}gma warning restore S3963 // "static" fields should be initialized inlinegma warning disable S2743 // Static fields should not be used in generic typesprivate static readonly string _CachedValue = "";gma warning restore S2743 // Static fields should not be used in generic typespublic 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
