原文出处我的wiki,转载请说明出处

考虑到泛型在集合类型中的广泛应用,这里一起讨论。

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 == ;
}
}

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

        /// <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
.locals init ([] class [mscorlib]System.Collections.Generic.List`<int32> values,
[] class [mscorlib]System.Collections.Generic.List`<string> strs)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`<int32>::.ctor()
IL_0006: stloc.
IL_0007: newobj instance void class [mscorlib]System.Collections.Generic.List`<string>::.ctor()
IL_000c: stloc.
IL_000d: ldloc.
IL_000e: call bool ConsoleApplication1.CollectionExt::IsNullOrEmpty<int32>(class [mscorlib]System.Collections.Generic.ICollection`<!!>)
IL_0013: call void [mscorlib]System.Console::WriteLine(bool)
IL_0018: nop
IL_0019: ldloc.
IL_001a: call bool ConsoleApplication1.CollectionExt::IsNullOrEmpty<string>(class [mscorlib]System.Collections.Generic.ICollection`<!!>)
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命名空间中的多个线程安全的集合类。

不急,慢慢来。

之前遇到几个工作七八年的竟然也不懂这些很基础的东西。

可以说劣币驱逐良币,也是离开目前公司的主要原因,不过社会本身如此。不管怎样,还是多看看、多学习,进而跳出牢笼,重新认识自我和社会,才有可能抓住转瞬即逝的机会。

从自身的角度,觉得技术和成长好厉害、解决很多问题,其实是没有用滴,老板考虑的始终是用较为低廉的薪水招到能做一般事情的人就行了,毕竟大家基本都是业务驱动的公司,能卖钱才是正道理,所以能说的永远比能做事情的机会多

2.1 IEnumerable

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

    [ComVisible(true)]
[Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerable
{
[DispId(-)]
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。

更多知识体系...

C# 泛型集合的更多相关文章

  1. C# DataSet装换为泛型集合

    1.DataSet装换为泛型集合(注意T实体的属性其字段类型与dataset字段类型一一对应) #region DataSet装换为泛型集合 /// <summary> /// 利用反射和 ...

  2. Linq操作非泛型集合

    我们都知道,Linq能查询泛型集合,确切的说是:LINQ能实现查询泛型对象或者实现了IEnumerable.但是,很遗憾的是诸如ArrayList这样的非泛型集合并没有实现IEnumerable.那咋 ...

  3. c#利用泛型集合,为自己偷偷懒。

    有人说"越懒"的程序员进步的越快!其实还挺有道理.亲身体验,从刚出来工作到现在,自己变"懒"了许多,但感觉写出来的代码确有了不少提升.刚开始啊,同样的代码,赋值 ...

  4. 2016年11月27日--面向对象:多态、类库、委托、is和as运算符、泛型集合

    1.虚方法 virtual 重写 override 父类中的方法,在子类中并不适用,那么子类需要自主更改继承的方法或者是属性,那父类中加了virtual关键字的方法才可以被子类重写,子类重写父类的方法 ...

  5. GsonUtils.getGson().fromJson() 转泛型集合

    List<QiTaFree> qiTaFreeList = GsonUtils.getGson().fromJson(exhiMain.getQiTaFressJson(), new Ty ...

  6. 快速入门系列--CLR--03泛型集合

    .NET中的泛型集合 在这里主要介绍常见的泛型集合,很多时候其并发时的线程安全性常常令我们担忧.因而简述下.NET并发时线程安全特性,其详情请见MSDN. 普通集合都不支持多重并发写操作 部分支持单线 ...

  7. 类库,委托,as.is,var,泛型集合

    类库: 就是让别人调用你写的方法,并且不让别人看到你是怎么实现的.(比如说一些核心文件) 如果有功能你不会做,需要别人帮忙,那么你的同事可以帮你写好一个类,然后你来调用这个类中的方法,完成你的项目. ...

  8. 面向对象——is和as运算符、泛型集合 List<T>

    二:is和as运算符: (1) is运算符 is 运算符用于检查对象是否与给定类型兼容.如果兼容返回true,否则返回false; 一般用于查看某个类是否实现了某个接口,或者是不是某个类的子类; 例如 ...

  9. Reflection和Expression Tree解析泛型集合快速定制特殊格式的Json

    很多项目都会用到Json,而且大部分的Json都是格式固定,功能强大,转换简单等,标准的key,value集合字符串:直接JsonConvert.SerializeObject(List<T&g ...

  10. C#—类库、委托、is和as运算符、泛型集合

    类库 类库(Class Library)是一个综合性的面向对象的可重用类型集合,这些类型包括:接口.抽象类和具体类.类库可以解决一系列常见编程任务(包括诸如字符串管理.数据收集.数据库连接以及文件访问 ...

随机推荐

  1. WSGIweb框架--1

    from multiprocessing import Process import re import sys import socket #设置静态文件根目录 HTML_ROOT_DIR = '. ...

  2. 实现quartz定时器及quartz定时器原理介绍(转)

    一.核心概念 Quartz的原理不是很复杂,只要搞明白几个概念,然后知道如何去启动和关闭一个调度程序即可.1.Job表示一个工作,要执行的具体内容.此接口中只有一个方法void execute(Job ...

  3. Linux基础教程

    Linux基础教程之<Linux就该这么学>之学习笔记第一篇... ========================= 一.Basic Linux Commands    基本的Linux ...

  4. TP-Link路由器无线WIFi的设置

    TP-Link路由器无线WIFi的设置.. ------------------ 确保网线接好,TP-LINK 路由器接好后,打开浏览器,在地址栏输入:192.168.1.1 输入用户名和密码(默认都 ...

  5. CSS关键词的值-currentColor关键字(当前颜色)

    currentColor关键字 currentColor关键字相当于一个CSS变量. currentColor关键字与CSS变量也是有区别的: (1)他只可以能接受<color>值的地方使 ...

  6. 一起来学Go --- (go的枚举以及数据类型)

    枚举 枚举指一系列的相关的常量,比如下面关于一个星期的中每天的定义,通过上篇博文,我们可以用在const后跟一对圆括号的方式定义一组常量,这种定义法在go语言中通常用于定义枚举值.go语言并不支持众多 ...

  7. 网络唤醒全攻略(Wake On Lan)

    家里组了台服务器存放资料,或者作为开发服务器,远程登陆成为刚性需求,由于机器需要的时候才用到,所以如果经常开机的话很费电,按需开机是最佳办法:网上教程很多,但是比较杂乱,表达累赘:还是自己总结一篇简单 ...

  8. javascript基础进阶——执行环境及作用域链

    概念 执行环境 执行环境定义了变量或函数有权访问的其他函数,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象. 变量对象 环境中定义的所有变量和函数都保存在这个对象中. 全局执行环境 全局 ...

  9. 如何编写更好的SQL查询:终极指南-第二部分

    上一篇文章中,我们学习了 SQL 查询是如何执行的以及在编写 SQL 查询语句时需要注意的地方. 下面,我进一步学习查询方法以及查询优化. 基于集合和程序的方法进行查询 反向模型中隐含的事实是,建立查 ...

  10. vs下开端口直接调试iis

    有些时候我们接口调试不想发布然后挂到iis下面,因为这样子调试有点麻烦,不是不可以调试.当然我们就希望在vs下直接运行直接打断点调试! 只需要三步就可以实现: 1)  找到这个文件 2)  打开上面文 ...