C#泛型 List<T>的本质 | List原理机制

首先我们来看看List<T>这个泛型类

根据下面代码总结:当我们在创建一个List<T>类型对象的时候,如果没有给List<T>指定一个初始长度,那么也就是说,当我们执行0参数的构造方法时,微软为我们创建了一个T类型的个数为0的数组

当我们调用Add方法时候,List会新建一个数组,然后把数组的长度设置为原来的二倍(如果原有的数组长度为0,那就默认将数组的长度设置为4,如果List的容量已经达到此时List容量的最大值,那么就会新构造了一个数组,数组的长度设置为原来的二倍,然后把元素拷贝过去,旧的数组将会给GC回收)

根据以下代码:我想证明的是:其实List<T>不过是对Array的进一步封装,说得再直接点,我愿意理解List<T>为Array的可扩充版本,然后扩展了一些方法;

那关于Array和List的选择,我重新做一个说明:

List是基于Array存在的,因此,在创建一个List对象时,需要耗费比Array相对更多的时间,以及更大的空间,因为List除了初始化内部的items外还需要初始化一些其他的属性。而且在方法调用时,这点我没有证实,只是一个猜测,List需要的是再去调用Array的相关方法,因此也许会存在方法调用的时间消耗问题。

因此,我的建议是:

如果初始化时确定大小,那么就使用Array。

如果初始化时不确定大小,那么就使用List。当然,其实完全可以自己去实现List中的数组扩充功能的,也许会更棒,因为我们没有必要去将Array每次都扩充为原来的二倍。

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{
    private static readonly T[] _emptyArray = new T[0];

    private const int _defaultCapacity = 4; //表示默认的初始容量,即内部容器数组的Length

    private T[] _items; //这个真正存储数据的内部数组

    private int _size; //表示List中存储元素的个数

    private int _version;//表示一个版本,当Add元素或者Remove元素等时候,会自增。我们在foreach list过程中如果list改变了,那么会抛出异常(好像是集合已修改,不能枚举),就是根据它来判断的。

    private List<T> _list;

    private object _root; //在调用Add方法等方法的时候,坐look锁用的


    public List() //List<T>泛型类的无参的构造方法
    {
        //当无参的时候,.NET Framework其实是用一个_emptyArray来初始化了List中的items集合。那么_emptyArray又是什么呢?
        //_emptyArray其实是一个T类型的数组,个数为0。那么也就是说,当我们执行0参数的构造方法时,系统是把items集合给赋值为了一个T类型的个数为0的数组
        this._items = List<T>._emptyArray;
    }

    public void Add(T item)
    {
        if (this._size == this._items.Length) //在新增的时候,判断一下_items中存储元素的个数是否等于_items数组的实际长度
        {
            this.EnsureCapacity(this._size + 1); //当空间满时,调整空间
        }
        T[] arg_36_0 = this._items;
        int size = this._size;
        this._size = size + 1;
        arg_36_0[size] = item;
        this._version++;
    }
    private void EnsureCapacity(int min)
    {
        if (this._items.Length < min)
        {
            int num = (this._items.Length == 0) ? 4 : (this._items.Length * 2); //新的数据的默认大小为4,或者在原来的的长度基础上长度加倍
            if (num > 2146435071)
            {
                num = 2146435071;
            }
            if (num < min)
            {
                num = min;
            }
            this.Capacity = num;
        }
    }

    public int Capacity
    {
        [__DynamicallyInvokable]
        get
        {
            return this._items.Length;
        }
        [__DynamicallyInvokable]
        set
        {
            if (value < this._size)
            {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
            }
            if (value != this._items.Length)
            {
                if (value > 0)
                {
                    T[] array = new T[value];
                    if (this._size > 0)
                    {
                        Array.Copy(this._items, 0, array, 0, this._size);
                    }
                    this._items = array;
                    return;
                }
                this._items = List<T>._emptyArray;
            }
        }
    }
}

 

 

泛型类是使用,及概念

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 泛型
{
    //定义一个泛型类,该类有两个类型参数,分别是T,S  
    public class Test<T,N>
    {
        private T Name { get; set; } 
        private N Age { get; set; }
        //构造函数,初始化两个属性
        public Test(T name,N age)
        {
            this.Name = name;
            this.Age = age;
        }

        public void SetValue()
        {
            Console.WriteLine("Hi,我的名字叫{0},我今年{1}岁", Name, Age);
        }
    
    }
    class Program
    {
        static void Main(string[] args)
        {
            //使用string,int来实例化Test<T,N>类。这里调用了带两个参数的构造函数
            Test<string, int> t = new Test<string, int>("tom", 25);
            t.SetValue();
            Console.ReadKey();                     
            
        }
    }
}


继承泛型类

 

 

 

 

上面的例子不是很恰当,目的是让初学泛型的你了解一下泛型的定义及实例化方法,如上,我们定义了一个泛型类,那么如何实现C#泛型类的继承呢?这里需要满足下面两点中的任何一点即可:

1、泛型类继承中,父类的类型参数已被实例化,这种情况下子类不一定必须是泛型类;

2、父类的类型参数没有被实例化,但来源于子类,也就是说父类和子类都是泛型类,并且二者有相同的类型参数;

 //如果这样写泛型继承的话,显然会报找不到类型T,S的错误。
    //public class TestChild : Test<T, S> { }

    //正确写泛型类继承的写法应该是这样的
    public class TestChild : Test<string, int> { }
    public class TestChild<T, S> : Test<T, S> { }
    public class TestChild<T, S> : Test<string, int> { }

 

继承泛型接口

接着我们来看看泛型接口,其创建以及继承规则和上面说的泛型类是一样的,看下面的代码:

接口中只能包含方法、属性、索引器和事件的声明。不允许声明成员上的修饰符,即使是pubilc都不行,因为接口成员总是公有的,也不能声明为虚拟和静态的。如果需要修饰符,最好让实现类来声明。
 

接口默认访问符是internal
接口的成员默认访问修饰符是public 

 

    public interface IList<T> 
    {
        T[] GetElements(); //定义了一个GetElements()方法,这个方法的返回值类型为T类型的数组。相当于 pulbic string[] GetElements()
    }

    public interface IDictionary<K, V> //定义一个泛型接口,该接口有两个类型参数,分别是K,V ;如果是普通的数组就这样声明public interface IDictionary
    {
        void Add(K key, V value);
    }

    //定义一个List<T>泛型类,让它继承IList<T>,IDictionary<int ,T>泛型接口
    //泛型接口的类型参数要么已经实例化
    //要么来源于实现类声明的类型化参数
    class List<T> : IList<T>, IDictionary<int, T> 
    {
        public T[] GetElements() //实现泛型接口
        {
            return null;
        }

        public void Add(int index, T value) //实现泛型接口
        {
            Console.WriteLine ("第{0}项的值是{1}",index,value);
        }
    }

 

 

 

 

 

 

 

 

 


 



  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值