C# 泛型集合

 原文出处个人wiki,转载请说明出处
html

 

考虑到泛型在集合类型中的普遍应用,这里一块儿讨论。git

1. 泛型

1.1 泛型的定义与约束

建立泛型方法、委托、接口或类时,须要在名称后增长尖括号及其中的泛型参数,泛型参数一般用T或T为前缀的描述性单词表示。程序员

    /// <summary>
    /// 集合类型扩展方法
    /// </summary>
    public static class CollectionExt
    {
        /// <summary>
        /// 集合类型判空
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static bool IsNullOrEmpty<T>(this ICollection<T> collection)
        {
            return collection == null || collection.Count == 0;
        }
    }

可用where来约束泛型继承的基类或实现的接口,而且可使用default(T)获取泛型的默认值。github

        /// <summary>
        /// 获取集合中按照某个数值类型比较 最小的元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static T MinItem<T>(this IEnumerable<T> items, Func<T, decimal> func) where T : class
        {
            List<decimal> list = new List<decimal>();
            if (items.IsNullOrEmpty())
            {
                return null;
            }

            decimal minValue = decimal.MaxValue;
            T minItem = default(T);
            foreach (var item in items)
            {
                var currentValue = func(item);
                if (minValue > currentValue)
                {
                    minItem = item;
                    minValue = currentValue;
                }
            }
            return minItem;
        }

1.2 泛型中的类型膨胀

面试的时候,经常会被问到C#和JAVA中泛型的差别,以及泛型参数类型在什么时候肯定。在C#中,仍然以第一段代码中集合类型判空为例分析。面试

    class Program
    {
        static void Main(string[] args)
        {
            var values = new List<int>();
            var strs = new List<string>();
            Console.WriteLine(values.IsNullOrEmpty());
            Console.WriteLine(strs.IsNullOrEmpty());
            Console.ReadKey();
        }
    }

查看IL代码。数组

  .entrypoint
  // 代码大小       44 (0x2c)
  .maxstack  1
  .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> values,
           [1] class [mscorlib]System.Collections.Generic.List`1<string> strs)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
  IL_0006:  stloc.0
  IL_0007:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
  IL_000c:  stloc.1
  IL_000d:  ldloc.0
  IL_000e:  call       bool ConsoleApplication1.CollectionExt::IsNullOrEmpty<int32>(class [mscorlib]System.Collections.Generic.ICollection`1<!!0>)
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_0018:  nop
  IL_0019:  ldloc.1
  IL_001a:  call       bool ConsoleApplication1.CollectionExt::IsNullOrEmpty<string>(class [mscorlib]System.Collections.Generic.ICollection`1<!!0>)
  IL_001f:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_0024:  nop
  IL_0025:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_002a:  pop
  IL_002b:  ret

能够看到,泛型的参数类型在编译时就已经肯定。安全

编译后生成的List`1<int32>和List`1<string>,是两个不一样的类型,这称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。性能

这就使得类型安全,避免了装箱和拆箱操做。学习

2. 集合类型

System.Array定义了数组类。

System.Collections命名空间中定义了处理object对象的经常使用集合类,在访问时需类型转换或装箱拆箱。

System.Collections.Generic命名空间中提供了不少集合类的泛型版本。

System.Collections.Specialized命名空间中定义了专用于特定类型的集合类。

.NET 4.0开始提供System.Collections.Concurrent命名空间中的多个线程安全的集合类。

不急,慢慢来。ui

 

以前遇到几个工做七八年的居然也不懂这些很基础的东西。

能够说劣币驱逐良币,也是离开目前公司的主要缘由,不过社会自己如此。无论怎样,仍是多看看、多学习,进而跳出牢笼,从新认识自我和社会,才有可能抓住转瞬即逝的机会。

从自身的角度,以为技术和成长好厉害、解决不少问题,实际上是没有用滴,老板考虑的始终是用较为低廉的薪水招到能作通常事情的人就好了,毕竟你们基本都是业务驱动的公司,能卖钱才是正道理,因此能说的永远比能作事情的机会多

2.1 IEnumerable

简单的说,实现此接口,才能支持遍历,接口返回的是一个IEnumerator。

    [ComVisible(true)]
    [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
    public interface IEnumerable
    {
        [DispId(-4)]
        IEnumerator GetEnumerator();
    }

 2.2 IEnumerator

真正的实现了遍历的过程,还延伸到yield的用法,这里再也不赘述。

    [ComVisible(true)]
    [Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
    public interface IEnumerator
    {
        object Current { get; }

        bool MoveNext();

        void Reset();
    }

Current当前枚举项的引用,初始化时还没有指向任何枚举项,每次访问时要转化为对应类型,故屡次使用当前枚举项时可先赋值给强类型的临时变量,但泛型版本则不须要这样作。

MoveNext()移动引用使其指向下一个枚举项,首次移动后指向第一个枚举项,重复整个过程直至当前枚举项为空实现对集合的遍历。

Reset()将当前枚举项的引用重置到初始状态。

 

从头至尾对一个集合进行枚举本质上并非一个线程安全的过程,即便一个集合自己是线程安全的,其余线程仍能够修改该集合,这将致使枚举数引起异常。若是说写七八年代码的连这都不懂,我信(被前前同事坑怕了,中毒太深)。

若要在枚举过程当中保证线程安全,能够在整个枚举过程当中锁定集合。

2.3 ICollection

    [ComVisible(true)]
    public interface ICollection : IEnumerable
    {
        int Count { get; }

        bool IsSynchronized { get; }

        object SyncRoot { get; }

        void CopyTo(Array array, int index);
    }
各类实现类中,有几个比较关键的属性:
Capacity属性表示的是集合的容量。 Count属性表示的是集合中当前数据项个数,按照各自的规则自动扩容。 Clear操做只是清空集合中的元素,但集合的容量没变,仍然占用着内存空间,相似于乘客从列车中出来,但列车的容量不变。 SyncRoot属性主要在须要线程安全的操做时使用。

2.4 IDictionary

属于一对一的字典,Keys和Values独立存储,经过对Key执行哈希获取Value的存储位置,因此Key能够是非null的任意类型,且不能有重复。

    [ComVisible(true)]
    [DefaultMember("Item")]
    public interface IDictionary : ICollection, IEnumerable
    {
        object this[object key] { get; set; }

        bool IsReadOnly { get; }

        ICollection Keys { get; }

        ICollection Values { get; }

        void Add(object key, object value);

        void Clear();

        bool Contains(object key);

        IDictionaryEnumerator GetEnumerator();

        void Remove(object key);
    }

在具体的实现类中:

Dictionary<TKey,TValue>采用链地址法处理哈希冲突,填充因子是1。

ConcurrentDictionary<TKey,TValue>线程安全的键值集合,TyrAdd、TryGetValue、TryRemove、TryUpdate方法以非阻塞的方式访问元素。

SortedList<TKey,TValue>用两个数组分别存储键和值并保持大小的同步性,因此对GC比较友好一点,且支持用索引或键查找值并按照键排序。

SortedDictionary<TKey,TValue>是一个按照键构造的二叉查找树,因此,添加或删除操做只会根据红黑树的特性旋转节点来保持平衡故性能优于SortedList而差于Dictionary,而查找时可利用二分法,结构上对GC而言相对比较复杂。

 

其中,Dictionary[key]=value至关于AddOrUpdate,属于引用替换,亲们能够考虑下是否是线程安全的。

 2.5 ILookup

属于一对多的字典类型。

    [DefaultMember("Item")]
    public interface ILookup<TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>>, IEnumerable
    {
        IEnumerable<TElement> this[TKey key] { get; }

        int Count { get; }

        bool Contains(TKey key);
    }

2.6 IList

提供对数据项的增删操做,可按照索引访问数据项。

    [ComVisible(true)]
    [DefaultMember("Item")]
    public interface IList : ICollection, IEnumerable
    {
        object this[int index] { get; set; }

        bool IsFixedSize { get; }

        bool IsReadOnly { get; }

        int Add(object value);

        void Clear();

        bool Contains(object value);

        int IndexOf(object value);

        void Insert(int index, object value);

        void Remove(object value);

        void RemoveAt(int index);
    }

索引符是一种特殊类型的属性,提供按照索引访问数据项的功能。

遍历删除的操做,能够考虑使用倒叙方式。

 2.7 ISet

不容许有重复的元素。

    public interface ISet<T> : ICollection<T>, IEnumerable<T>, IEnumerable
    {
        bool Add(T item);

        void ExceptWith(IEnumerable<T> other);

        void IntersectWith(IEnumerable<T> other);

        bool IsProperSubsetOf(IEnumerable<T> other);

        bool IsProperSupersetOf(IEnumerable<T> other);

        bool IsSubsetOf(IEnumerable<T> other);

        bool IsSupersetOf(IEnumerable<T> other);

        bool Overlaps(IEnumerable<T> other);

        bool SetEquals(IEnumerable<T> other);

        void SymmetricExceptWith(IEnumerable<T> other);

        void UnionWith(IEnumerable<T> other);
    }

这里牢骚几句,好多程序员处理去重时,都用dictionary(键值同样),明明有set不用,真捉急。

还有一些场景,好比电商产品起价的更新时,须要对数据取交集、并集、差集的运算,此时set也颇有妙用,能够将复杂度从N^2下降到N。

 

更多知识体系...

相关文章
相关标签/搜索