C#泛型理解
“泛型是2.0及以上版本C#编程语言和CLR提供的一种特殊机制,他支持另一种形式的代码重用,即’算法重用’ ”。(From《CLR via C#》)
泛型实现了构造泛型类、方法、接口等未指定数据类型,到使用时才指定,它完美体现了设计思想——延迟声明:延迟一切可以延迟的。
泛型不是简单的语法糖,它的实现有赖于.NET框架升级支持。
命名空间:using System.Collections.Generic
一、泛型应用场景
static void Main(string[] args)
{
Printf("你好~");
Printf(1);
Printf(false);
Console.ReadKey();
}
private static void Printf(string input)
{
Console.WriteLine("输入为:{0},{1}" ,input,input.GetType());
}
private static void Printf(int input)
{
Console.WriteLine("输入为:{0},{1}", input, input.GetType());
}
private static void Printf(bool input)
{
Console.WriteLine("输入为:{0},{1}", input, input.GetType());
}
可以看出,以上三个方法的区别只在于传入的参数类型不一样,方法内实现的功能是一样的,这个时候我们有两种方法,一是将传入参数类型声明为object,另一种是使用泛型。
为什么不建议使用object?
static void Main(string[] args)
{
//正常输出
object input = "你好~";
Printf(input);
//强制转换,编译时不报错,但运行时出错
int nStr = (int)input;
Printf(nStr);
}
private static void Printf(object input)
{
Console.WriteLine("输入为:{0},{1}" ,input,input.GetType());
}
由此可以看到使用object需要强制转换,强制转换的装箱拆箱会造成很多性能损耗,除了性能外,object不能保证类型安全,强制转换就是告诉编译器,这样的转换是安全的,到运行时转换失败,就会造成数据丢失。
二、泛型的优势
1.源代码保护;
2.类型安全;
3、代码更清晰、易维护性能
4.性能更佳。
下面代码展示了一下泛型List和非泛型ArrayList的性能差异。
using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
namespace Test
{
class Program
{
static void Main(string[] args)
{
const Int32 count = 100000000;
using (new MyTimer("List<Int32>"))
{
List<Int32> l = new List<int>();
for (Int32 i = 0; i < count; i++)
{
l.Add(i); //不用装箱
Int32 n = l[i]; //不用拆箱
}
l = null;
}
using (new MyTimer("ArrayList"))
{
ArrayList array = new ArrayList();
for (Int32 i = 0; i < count; i++)
{
array.Add(i); //发生装箱
Int32 n = (Int32)array[i]; //发生拆箱
}
}
Console.ReadKey();
}
}
/// <summary>
/// 用于计算性能用时的自定义类
/// </summary>
internal sealed class MyTimer : IDisposable
{
private Stopwatch stopwatch;
private string text;
private Int32 collectionCount;
public MyTimer(string _text)
{
PrepareForAction();
text = _text;
collectionCount = GC.CollectionCount(0);
stopwatch = Stopwatch.StartNew();
}
public void Dispose()
{
Console.WriteLine("{0} (GCs={1,3}) {2}", stopwatch.Elapsed, GC.CollectionCount(0) - collectionCount, text);
}
/// <summary>
/// 进行垃圾回收
/// </summary>
private static void PrepareForAction()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
}
从运行结果可以看出,操作值类型时,泛型List算法比非泛型ArrayList快多了,0.96和7.6秒的差异,此外,操作非泛型造成大量装箱拆箱,最后要进行388次垃圾回收,而泛型算法只需要8次。
不过,对于引用类型数据,差异不明显,垃圾回收次数和时间都差不多(你们可以自测一下)
三、泛型的几种应用
泛型的应用有泛型集合类,泛型接口,泛型方法。
/// <summary>
/// 泛型方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParameter)
{
Console.WriteLine("This is parameter={0},type={1}",tParameter.GetType().Name, tParameter.ToString());
}
/// <summary>
/// 泛型类
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericClass<T>
{
public T _T;
}
/// <summary>
/// 泛型接口
/// </summary>
public interface IGenericInterface<T>
{
//泛型类型的返回值
T GetT(T t);
}
public delegate void SayHi<T>(T t);//泛型委托
虽然能自定义泛型委托,但最好是使用框架自带的委托。
四、泛型约束
泛型约束格式:使用where关键字
泛型约束的意义:保证传递的类型参数符合一定的条件,能在编译阶段对传入的参数进行验证
类型 | 约束格式 | 示例 |
---|---|---|
接口约束 | T:<接口名称> | public class MyGenericClass where T:IComparable { } |
基类约束 | T:<基类名> | class MyClassy<T, U> where T : class |
引用约束 | T:class | public class BaseAccess where T : class{} |
值约束 | T:struct | public class MyClass9 where T : struct { } |
构造函数约束 | T:new() | public class MyGenericClass where T: IComparable, new(){T item = new T(); } |
裸类型约束 | T:U | class List{void Add(List items) where U : T { }} |
泛型约束注意事项:
1.多个约束
不可能同时添加struct和class约束
不可能添加多个基类约束
约束之间是 and 关系,不能添加or关系的约束
构造函数约束必须最后
构造函数约束只能指明无参构造器
约束不会继承
2.多个参数
对于一种表示“键值对”关系的泛型类KeyValue<TKey,TValue>,该类具有两个泛型参数TKey,TValue,要为其都添加约束。