C# 泛型集合
考虑到泛型在集合类型中的广泛应用,这里一起讨论。
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# 泛型集合的更多相关文章
- C# DataSet装换为泛型集合
1.DataSet装换为泛型集合(注意T实体的属性其字段类型与dataset字段类型一一对应) #region DataSet装换为泛型集合 /// <summary> /// 利用反射和 ...
- Linq操作非泛型集合
我们都知道,Linq能查询泛型集合,确切的说是:LINQ能实现查询泛型对象或者实现了IEnumerable.但是,很遗憾的是诸如ArrayList这样的非泛型集合并没有实现IEnumerable.那咋 ...
- c#利用泛型集合,为自己偷偷懒。
有人说"越懒"的程序员进步的越快!其实还挺有道理.亲身体验,从刚出来工作到现在,自己变"懒"了许多,但感觉写出来的代码确有了不少提升.刚开始啊,同样的代码,赋值 ...
- 2016年11月27日--面向对象:多态、类库、委托、is和as运算符、泛型集合
1.虚方法 virtual 重写 override 父类中的方法,在子类中并不适用,那么子类需要自主更改继承的方法或者是属性,那父类中加了virtual关键字的方法才可以被子类重写,子类重写父类的方法 ...
- GsonUtils.getGson().fromJson() 转泛型集合
List<QiTaFree> qiTaFreeList = GsonUtils.getGson().fromJson(exhiMain.getQiTaFressJson(), new Ty ...
- 快速入门系列--CLR--03泛型集合
.NET中的泛型集合 在这里主要介绍常见的泛型集合,很多时候其并发时的线程安全性常常令我们担忧.因而简述下.NET并发时线程安全特性,其详情请见MSDN. 普通集合都不支持多重并发写操作 部分支持单线 ...
- 类库,委托,as.is,var,泛型集合
类库: 就是让别人调用你写的方法,并且不让别人看到你是怎么实现的.(比如说一些核心文件) 如果有功能你不会做,需要别人帮忙,那么你的同事可以帮你写好一个类,然后你来调用这个类中的方法,完成你的项目. ...
- 面向对象——is和as运算符、泛型集合 List<T>
二:is和as运算符: (1) is运算符 is 运算符用于检查对象是否与给定类型兼容.如果兼容返回true,否则返回false; 一般用于查看某个类是否实现了某个接口,或者是不是某个类的子类; 例如 ...
- Reflection和Expression Tree解析泛型集合快速定制特殊格式的Json
很多项目都会用到Json,而且大部分的Json都是格式固定,功能强大,转换简单等,标准的key,value集合字符串:直接JsonConvert.SerializeObject(List<T&g ...
- C#—类库、委托、is和as运算符、泛型集合
类库 类库(Class Library)是一个综合性的面向对象的可重用类型集合,这些类型包括:接口.抽象类和具体类.类库可以解决一系列常见编程任务(包括诸如字符串管理.数据收集.数据库连接以及文件访问 ...
随机推荐
- WebBrowser keystroke
问题描述:将对话框中的回车消息传递到WebBrowser控件中去. 一开始的解决方案是:通过iwebbrowser2的成员函数get_HWND获取控件的句柄,然后通过SendMessage发送按键消息 ...
- 神经网络与深度学习笔记 Chapter 3.
交叉熵 交叉熵是用于解决使用二次代价函数时当单个神经元接近饱和的时候对权重和bias权重学习的影响.这个公式可以看出,当神经元饱和的时候,sigma的偏导接近于0,w的学习也会变小.但是应用交叉熵作为 ...
- 实现多条件模糊查询SQL语句
很多网友问到如何写模糊查询语句和多条件查询,这里我整理了一下,假设以姓名.性别.电话号...作为数据库中的字段名. 通常写一个简单的模糊查询的SQL语句格式可以如下例: sql="selec ...
- Shrio认证详解+自定义Realm
Authentication(身份认证)是Shiro权限控制的第一步,用来告诉系统你就是你. 在提交认证的时候,我们需要给系统提交两个信息: Principals:是一个表示用户的唯一属性,可以是用户 ...
- 【BZOJ3262】陌上花开 (CDQ分治+树状数组+排序)
Time Limit: 3000 ms Memory Limit: 256 MB Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),用三个整数表示. 现要对每 ...
- org.apache.commons.lang.StringUtils 中 Join 函数
转自 http://my.oschina.net/zenglingfan/blog/134872 写代码的时候,经常会碰到需要把一个List中的每个元素,按逗号分隔转成字符串的需求,以前是自己写一段比 ...
- Ext.grid.EditorGridPanel保存
用get方法传递编辑的数据会出现乱码,解决get乱码的方法就是encodeURI(param),然后在后台转码: String strJson = new String(request.getPar ...
- Linux的vi常用命令详解
1.vi的基本概念 基本上vi可以分为三种状态,分别是命令模式(command mode).插入模式(Insert mode)和底行模式(last line mode),各模式的功能区分如下: ...
- h5audio标签
因为音频格式有版权,各浏览器使用不同的音频格式. 音频格式兼容性 音频格式 Chrome Firefox IE9 Opera Safari MP3 支持 不支持 支持 不支持 支持 OGG 支持 支持 ...
- oracle 索引失效的情况分析
见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp54 1) 没有查询条件,或者查询条件没有建立索引 2) 在查询条件上 ...