(转载)浅谈C#中的泛型
本文记录一些自己读书中感觉脑神经一跳的地方,但都还是比较浅显的东西。由于自己在泛型使用上还火候未到,什么地方有错误还请各位纠正,高手请勿喷啊。
什么是泛型?
泛型是.NET Framework 2.0新增的一个东西,泛型与程序集中的IL 代码高度集成。有了泛型,我们可以创建独立于被包含类型的类和方法,我们不必给不同的类编写功能相同的很多方法或者类,只创建一个方法或类就可以了。和Object类相比,Object类属于非类型安全的,而泛型使用泛型类型,可以根据需要用特定的类型替换泛型类型,这样就保证了类型安全类。另一方面,泛型和C++的模板很相似,但泛型是由CLR定义的。
泛型LIST<T>与集合ArryList的比较
ArryList 可以存储值类型或者引用类型,但对于值类型存入要进行一次装箱的过程。取出的时候也要进行拆箱操作。
var list = new ArrayList(); list.Add(123); //添加int型的123,将int装箱为object foreach (int item inlist) //取出list[0]为item,将object拆箱为int { Console.WriteLine(item); }
拆箱装箱的操作自然会影响程序的运行效率。而在向Arrylist里添加成员的时候它不会判断数据类型,也就是不管什么类型的对象都可以添加到Arrylist里,当arrylist里的数据类型不相同时,程序运行时会出现异常。
var list = new ArrayList(); list.Add("string1"); list.Add(123); list.Add(DateTime.Now); list.Add(new MyClass()); foreach (int item in list) //当转换MyClass的事例时,系统会抛出异常。 { Console.WriteLine(item); }
相对于ArryList,List<T>中,泛型类型T定义了允许使用的类型,当定义List<int> 的一个泛型对象,它就只允许int的类型传入,如果尝试传入其他类型的对象,编译时不会通过,因为参数是无效的。
List<T>的内存
首先在创建List对象的时候,系统只会为List对象本身分配内存,其Capacity属性也为0。当为List对象传入第一个成员后,会为其开辟长度为4的内存空间,同样Capacity属性值也为4。当继续为其增加元素总数超过当前Capacity个的话,List对象会扩充为2倍当前Capacity的容量,也就是说当向list添加第5个元素后,这个List的Capacity是8。 但List内的元素内存是一直保持连续的,所以每一次扩充容量,都会先开辟一块是原来容量两倍大的内存,然后把之前的数据拷贝到新开辟的内存中,这样一来,如果List内的数据量很大的话,也会是一个很大的开销。
但是如果每次添加元素超过当前Capacity,都会申请一块两倍大的内存,有时可能会造成对内存的浪费。这时我们可以使用List提供的方法TrimExcess,来使List对象的容量等于元素的数量。但是,重新分配和复制很大的List 的开销可能很大,因此,如果列表大于容量的 90%,则 TrimExcess 方法将不执行任何操作,也就是说如果未用容量已经小于等于总容量的 10%,则列表容量不会进行调整。这样可以避免为获得相对较少的收益而产生大量重新分配开销。 请看以下代码比较好的说明前面提到的。
List<int> value = new List<int>(); //此时 Count:0 Capacity: 0 for (int i = 1; i <= 4; i++) { value.Add(i); } //此时 Count:4 Capacity: 4 value.Add(5); ////此时 Count:5 Capacity: 8 value.TrimExcess(); //此时 Count:5 Capacity: 5 //remove an item value.RemoveAt(4); //此时 Count:4 Capacity: 5 value.TrimExcess(); //此时 Count:4 Capacity: 5 未用容量小于总容量的 10% //remove another item value.RemoveAt(1); //此时 Count:3 Capacity: 5 value.TrimExcess(); //此时 Count:3 Capacity: 3 value.Clear(); //此时 Count:0 Capacity: 3 value.TrimExcess(); //此时 Count:0 Capacity: 0
泛型类
为什么说使用泛型会使程序变得更加灵活呢?看一下以下定义了一个泛型类。
public class MyClass<T> { public MyClass(T value) { this.Value = value; } public T Value { get; set; } }
名为MyClass 的类为一个泛型类,因为他用泛型类型T声明,这样泛型类型就可以在类中作一个字段成员,其构造函数也可以接受T类型的对象,T类型也就是所有类型。下面例子一个DocumentManager泛型类。
public class DocumentManager<T> //创建泛型类 { private Queue<T> documentQueue=new Queue<T>(); public void AddDocuemnt(T doc) //创建泛型方法 { lock(this) { documentQueue.Enqueue(doc); } } public bool IsDocumentAvailable { get { return documentQueue.Count > 0; } } public T GetDocument() { T doc = default(T); //默认值Default关键字,不能把null赋予泛型类型,因为泛型类型也可以实例化为值类型,通过default将null赋予引用类型,0赋予值类型。 lock (this) { doc = documentQueue.Dequeue(); } return doc; }
泛型约束,如果泛型类要调用泛型类型中的方法,就必须添加约束,下面代码Document实现IDocument接口。
public interface IDocument { string Title { get; set; } string Content { get; set; } } public class Document:IDocument { public Document() { } public Document(string title,string content) { this.Title = title; this.Content = content; } public string Title { get; set; } public string Content { get; set; } }
然后在DocumentManager类里添加方法用于显示所有文档的Title
public void DisplayAllDocumets() { foreach (T doc in documentQueue) { Console.WriteLine(((IDocument)doc).Title); } }
但是如果类型T没有实现IDocument接口,运行程序是会抛出异常。这就需要为DocumentManager定义一个约束:(这里将T改为TDocument只是为了更方便阅读)
public class DocumentManager<T> where T:IDocument
这样一来,就只接受实现了IDocument的参数,我们就可以改写DisplayAllDocumets方法如下:
public void DisplayAllDocumets() { foreach (T doc in documentQueue) { Console.WriteLine(doc.Title); //可以直接调用Title属性 } }
在Main方法中来试一下
static void Main(string[] args) { var dm = new DocumentManager<Document>(); dm.AddDocuemnt(new Document("Title A", "Content A")); dm.AddDocuemnt(new Document("Title B", "Content B")); dm.AddDocuemnt(new Document("Title C", "Content C")); dm.DisplayAllDocumets(); if (dm.IsDocumentAvailable) { Document d = dm.GetDocument(); Console.WriteLine(d.Content); } Console.ReadLine(); }
输出:
下图为泛型支持的几种约束类型
当使用多个约束时,是用逗号隔开的。例如:
public class DocumentManager<T> where T:IDocument,new() //多个约束
泛型类中的静态成员 只能在相同泛型类型的实例中共享,
public class StaticDemo<T> { public static int x; } class Program { private static void Main(string[] args) { StaticDemo<string>.x = 4; StaticDemo<int>.x = 5; Console.WriteLine(StaticDemo<string>.x); //输出4 Console.WriteLine(StaticDemo<int>.x); //输出5 StaticDemo<string>.x+=4; Console.WriteLine(StaticDemo<string>.x); //输出8 } }
下面的代码常用于Web application取Session方法。
public static T GetSession<T>(string name) { if (HttpContext.Current.Session != null && HttpContext.Current.Session[name] != null) return (T)HttpContext.Current.Session[name]; return default(T); }
GetSession方法将返回一个泛型类型的对象,比如我们有一个用户类UserInfo,并且再用户登陆后将UserInfo的一个实例存储到Session["User"],我们可以这样取出它。
var CurrentUser = GetSession<User>("User");