NullableKey:解决Dictionary中键不能为null的问题 zt
2012-12-29 02:26 by 老赵, 1745 visits
众所周知,.NET中Dictionary的键不能为null,否则会抛出NullReferenceException,这在某些时候会显的很麻烦。与此相对的是Java中的HashMap支持以null为键,则方便许多。尽管null的确不是个好东西,但它既然已经存在,既然给我们造成了麻烦,我们就要想办法去解决它。实现一个自己的字典类自然可行,但要精心实现一个高效的字典并不是件容易的事情,例如BCL中的Dictionary.cs就有超过2000行代码。此外另一个容易想到的方法便是实现IDictionary接口,将大部分实现委托给现成的Dictionary类来完成。不过,这相比我在这里要提出的方法还是显得太复杂了。
在编程语言中引入null其实是件很自然的事情,因为我们都在冯诺依曼机上进行开发,内存的访问方式便是“地址”,于是便有了null或NIL等类似的事物来表示一个指针没有指向任何一块地址,但与之相伴的便是各类错误。毕竟null这玩意儿过于透明,编译器在许多时候没法通过静态分析来检查出问题,所以在一些“非冯模型”的编程语言里都会避免使用null。例如在Haskell中,就使用Maybe这种数据类型来代替null。假如在C#中来模拟Maybe的话,其实就类似于:
public abstract class Maybe<T> { }
public sealed class Nothing<T> : Maybe<T> {
public static Nothing<T> Instance = new Nothing<T>();
private Nothing() { }
}
public sealed class Just<T> : Maybe<T> {
private readonly T _value;
public Just(T value) {
_value = value;
}
public T Value { get { return _value; } }
}
在Maybe类型中没有null,“有值”则是Just,“无值”则是Nothing。而T跟Maybe<T>并不兼容,再获取一个Maybe<T>类型的数据之后,则必须在逻辑分支里对Nothing和Just两种情况进行处理,于是就不会出现NullReferenceException。从理论上说,我们也可以使用这种方式来解决Dictionary中键不能为null的问题,只要用Dictionary<Maybe<TKey>, TValue>来代替Dictionary<TKey, TValue>即可,但实际上这种方式还是有两个缺点:
- 我们还是可以向字典的键传递
null。不过幸运的是,编译器不会把TKey的null当做Maybe<TKey>的null来使用,因此更大的问题在于: - 除了
Nothing以外,我们每次都要创建一个新对象,每个新对象都占用两个额外的字长(即8个或16个字节),这对GC来说会带来压力。
不过以上两个问题的解决办法也是显而易见的,那就是使用struct来代替class。在C#中有两个常被忽视,但对于性能有莫大关系的能力,一是unsafe代码,二便是可自定义的struct类型。struct不会对GC造成压力,并且不会占用额外的内存,可能唯一的问题便是用作泛型时会生成一份额外的可执行代码,且无法继承了。
无法继承没有关系,其实我们也不需要严格按照Maybe的数据模型来实现,只要能够解决问题即可。例如,我们可以使用这么一个NullableKey类型:
public struct NullableKey<T> {
private readonly T _value;
public NullableKey(T value) {
_value = value;
}
public T Value {
get { return _value; }
}
}
重载了GetHashCode和Equals方法以后,我们便可以使用NullableKey<T>来代替普通的T作为Dictionary的键。更有意思的是,我们可以为定义Nullable<T>与T之间的隐式转换,这样在很多场合下可以方便我们编写代码,例如:
var dict = new Dictionary<NullableKey<string>, int>();
dict[null] = 1; foreach (string key in dict.Keys) {
Console.WriteLine(key ?? "<null>");
}
由于Dictionary还支持使用自定义的IEqualityComparer类型,因此我也提供了一个配套的NullableKeyEqualityComparer<T>类,可以用来封装一个自定义的IEqualityComparer<T>,并提供IEqualityComparer<NullableKey<T>>的功能,例如:
var stringComparer = StringComparer.OrdinalIgnoreCase; // IEqualityComparer<string>
var keyComparer = new NullableKeyEqualityComparer<string>(stringComparer);
var dict = new Dictionary<NullableKey<string>, int>(keyComparer);
NullableKey和NullableKeyEqualityComparer的代码我已经提交至GitHub里的Tmc项目里。所谓Tmc,即缩写之The Missing Collections,我会放一些平时较为常用的,但BCL以及Power Collections或C5等常用第三方类库所没有提供的集合。东西不会多(毕竟已经有BCL和第三方类库了),但总比每次都重写要好。也希望大家也可以帮我审阅代码,尤其要着重检查效率方面的问题。毕竟是通用类库,我希望可以在效率方面也有很好的保证。
NullableKey:解决Dictionary中键不能为null的问题 zt的更多相关文章
- C#在Dictionary中使用枚举作为键
Enum类型没有实现IEquatable接口,Dictionary中使用Enum作为键时,将发生装箱,使效率降低. 此时可用Dictionary中一个接收IEqualityComparer<T& ...
- 如何解决数据库中,数字+null=null
如何解决数据库中,数字+null=null 我使用SQLServer,做一个 update 操作,累计一个数.在数据库中,为了方便,数据库中这个字段我设为允许为空,并且设置了默认值为 0 .但是在新增 ...
- Dictionary读取键值的快捷方法
对泛型集合Dictionary<T,T> 进行读取键值是经常的操作,一般情况下,都是通过keys 和values进行键值的读取操作: eg: foreach (var item in di ...
- 解决hibernate中的懒加载(延迟加载)问题
解决hibernate中的懒加载(延迟加载)问题 我们在开发的时候经常会遇到延迟加载问题,在实体映射时,多对一和多对多中,多的一样的属性默认是lazy="true"(即,默认是 ...
- 解决JQuery中datatables设置隐藏显示列多次提交后台刷新数据的问题
此次项目开发过程中用到了Jquery的Datatables插件,无疑他是数据列表展示,解决MVC中同步过程中先走控制器后返回视图,查询数据过程中无法提示等待的弊端, 而且他所提供的各种方法也都有较强的 ...
- 使用Ninject来解决程序中组件的耦合问题
1.为什么要用Ninject? Ninject是一个IOC容器用来解决程序中组件的耦合问题,它的目的在于做到最少配置.其他的的IOC工具过于依赖配置文件,需要使用assembly-qualified名 ...
- 解决iOS中tabBarItem图片默认颜色的问题(指定代码渲染模式为以原样模式的方式显示出来)
解决iOS中tabBarItem图片默认颜色的问题(指定代码渲染模式为以原样模式的方式显示出来) 解决办法:指定图片的渲染模式(imageWithRenderingMode为:UIImageRende ...
- 解决sql中上下左右backspace不能用的方法
一. 解决输入 BACKSPACE 键变成 ^h 的问题 #su - oracle $stty erase ^h. 要永久生效,可以加入到用户环境配置文件 .bash_profile 中 , 加入如下 ...
- inotify+rsync实现实时同步(附解决crontab中无法执行python脚本的问题)
1.准备环境 # 系统支持的话,下面的目录就会存在 ls /proc/sys/fs/inotify/ rpm -qa inotify-tools yum -y install inotify-tool ...
随机推荐
- 《APUE》第四章笔记(1)
1.引言 本章介绍文件系统的特征和文件的性质.从stat函数开始,逐个说明stat结构的每一个成员以了解文件的所有属性.在此过程中,还将会说明修改这些属性的各个函数,并更详细地查看UNIX文件系统的结 ...
- 鸟哥笔记:linux系统日志文件介绍
简单的说日志文件就是记录系统活动信息的几个文件,例如:何时.何地(来源ip).何人(什么服务名称).做了什么操作.换句话说就是:记录系统在什么时候由哪个进程做了什么样的行为时,发生了什么事件等. 日志 ...
- asp.net <% %>,<%# %>,<%= %>,<%$ %>区别大集合
前台页面 <div><%--可以执行服务器代码,相当于在后台写代码,Render%><%=取后台变量或方法值,只能绑定客户端控件,绑定服务器控件时后来必须调用databi ...
- Java琐记
svn项目倒入,所选的文件夹一定是src上面以及的:然后eclipse会自动创建一个项目,项目名称就是src上级文件夹的名称:然后会按照路径下的文档结构如导入到eclipse的结构中: 被标记为// ...
- ionic+angulajs
基于ionic+angulajs的混合开发实现地铁APP 项目源码地址:https://github.com/zhangxy1035/SubwayMap 一.项目简介 在该项目中的地铁app是基于io ...
- 仿window阿里旺旺登陆界面,打印机吐纸动画效果-b
偶然的机会发现window的阿里旺旺的登陆效果蛮有意思的,于是就模仿着做了一下打印机吐纸的动画效果看起来很神奇的东西,实现起来却不难,下面我给大家看下主要的源码. - (void)createUI{ ...
- caffe之(四)全连接层
在caffe中,网络的结构由prototxt文件中给出,由一些列的Layer(层)组成,常用的层如:数据加载层.卷积操作层.pooling层.非线性变换层.内积运算层.归一化层.损失计算层等:本篇主要 ...
- SpringMVC+Hibernate架构save方法事务未提交
今天同事遇到一个问题,一起研究,最后解决,让我对spring的事务管理又加深了印象. 先简单说一下项目:项目是Spring和Hibernate集成的JavaEE项目,MVC架构. 外包在service ...
- 使用struts2标签<s:action无法显示引用页面问题
使用过程中参考:http://www.cnblogs.com/lihuiyy/archive/2012/03/23/2411601.html 个人使用: 1.引用页面 <s:action nam ...
- matlab高斯模板生成,K是归一化系数,上面是一个半径200的高斯模板用来做MSR
R3=; F=zeros(*R3+,*R3+); sigma=R3/; r=; :*R3+ :*R3+ r=(x-R3)*(x-R3)+(y-R3)*(y-R3); ...