《C#高级编程》 读书心得2 -- 浅谈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");
posted @ 2012-11-07 21:40  Heylon  阅读(2074)  评论(3编辑  收藏  举报