.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 名称空间下找到。
泛型工作机制#
泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。 延迟思想在程序架构设计的时候很受欢迎。例如:分布式缓存队列、EF的延迟加载等等。
控制台程序最终会编译成一个exe程序,exe 被点击时,会经过JIT(即时编译器)的编译,最终生成二进制代码,才能被计算机执行。泛型加入到语法后,VS自带的编译器又做了升级,升级之后编译时遇到泛型,会做特殊的处理:生成占位符。再次经过JIT编译的时候,会把上面编译生成的占位符替换成具体的数据类型。
泛型方法#
public class Print
{
public void PrintType<T>() where T : new()
{
T t = new();
Console.WriteLine(t.GetType());
}
}[TestMethod]
public void GenericityMethod()
{
Print p = new();
p.PrintType<int>();
p.PrintType<double>();
}泛型类#
public class MyArray<T>
{
public T t;
}[TestMethod]
public void GenericityClass()
{
MyArray<int> genericInt = new();
genericInt.t = 123;
Console.WriteLine(genericInt.t);
MyArray<string> genericStr = new();
genericStr.t = "123";
Console.WriteLine(genericStr.t);
}default#
default 用于将泛型类型初始化为 null 或 0 。
T t = default(T);泛型接口#
public interface IDAL<T>
{
void Add(T t);
}
public class ClassADAL<T> : IDAL<T>
{
public void Add(T t)
{
Console.WriteLine(t);
}
}
public class ClassBDAL<T> : IDAL<T>
{
public void Add(T t)
{
Console.WriteLine(t);
}
}泛型委托#
public delegate void SayHi<T>(T t);[TestMethod]
public void GenericityDelegate()
{
SayHi<int> a = new((p) => { Console.WriteLine(p); });
a.Invoke(123);
SayHi<string> b = new((p) => { Console.WriteLine(p); });
b.Invoke("123");
}注意事项#
泛型在声明的时候可以不指定具体的类型,但是在使用时必须指定具体类型。
Print<int> iPrint = new Print<int>();这里的类型是int,如果存在继承并且子类也是泛型的,那么继承的时候可以不指定具体类型
class Print<T>
{
public T t;
}
//这里继承时没有指定泛型类型
class ConsolePrint<T> : Print<T>
{
}同上,类实现泛型接口也是如此。
泛型约束#
所谓的泛型约束,就是约束类型 T ,使 T 必须遵循一定的规则。比如 T 必须继承自某个类,或者 T 必须实现某个接口等。
| 类型 | 描述 |
|---|---|
| T:calss | 类型参数必须是引用类型,包括任何类、接口、委托或数组类型 |
| T:stauct | 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型 |
| T:new() | 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定 |
| T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类 |
| T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的 |
| T1:T2 | 类型T1派生自泛型类型T2,该约束也被称为裸类型约束 |
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: 协变,用来修饰返回值in:逆变,用来修饰传入参数
在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);
}