Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1688259
  • 博文数量: 177
  • 博客积分: 9416
  • 博客等级: 中将
  • 技术积分: 2513
  • 用 户 组: 普通用户
  • 注册时间: 2006-01-06 16:08
文章分类

全部博文(177)

文章存档

2013年(4)

2012年(13)

2011年(9)

2010年(71)

2009年(12)

2008年(11)

2007年(32)

2006年(25)

分类:

2007-12-20 11:37:08

出于对数据库访问的需要,C#中定义了一种类型,称为nullable类型。比如,在访问数据库时,需要返回某个整型字段的值。但是,当数据库中不存在符合条件的纪录时,返回的将是null。这样,以下这段代码便是非法:
int result = aDbCmd.ExecuteScalar(); // aDbCmd.ExecuteScalar() returns null to indicate no result fetched.

MS提到了普遍使用的三种解决方法:
1、用一个特殊值来表示。缺点在于你需要确定确实有一个值,除了用来指示是否为空外永远不会被用到;
2、使用一个bool型的字段或者变量来指示。缺点在于难于作为参数和返回值使用;
3、定义一个自定义的可空类型。但只能用于一个封闭的,无法拓展的类型集合。

于是MS使用了nullable类型。其实在我看来,MS只不过是利用了它对C#的控制,扩充了第三种解决方法,将某种东西附加到每个non-nullable的类型上。在C#中,nullable的表示是在类型之后加上“?”。可以认为以下两种定义是等价的:
T?等价于
struct nullable
{
    private T value;
    private bool nullIndicator;
    public T Value
    {
        get
        {
            if (!nullIndicator)
                throw new InvalidOperationException();
            return value;
        }
    }
    public bool HasValue
    {
       get { return nullIndicator; }
    }
    public nullable(T aValue)
    {
       value = aValue;
       nullIndicator = true;
    }
    public nullable()
    {
       value = UnInitializedT;
       nullIndicator = false;
    }
    // ...
}

也就是说,T? anObj与nullable anObj是等价的。

对于nullable对象的使用,如下:
int? x = 123;
int? y = null;
if (x.HasValue) Console.WriteLine(x.Value);
if (y.HasValue) Console.WriteLine(y.Value); // Nothing written.

好了,有了这些,我们就可以开始考察关于nullable的一些规则了。首先解释几个概念:
1、nullable转换和提升的转换。这两种类型转换允许预定义和自定义的转换同时能够用于non-nullable的值类型和这些类型的nullable形式。
2、提升的操作符。允许预定义和自定义的操作符同时能够用于non-nullable的值类型和这些类型的nullable形式。
3、null传播。直接将null转换成null,否则执行底层类型的non-nullable的转换。

概念也清楚了,本质也清楚了,那么就不难搞懂下面这些规则了:
1、底层的类型必须是non-nullable的值类型。啥也不用解释了,看看上面关于nullable的定义就明白。
2、隐含的类型转换。比如: int? a = 123;这里将一个字面常量隐式转换成nullable的int,其实这里就是创建一个nullableT,并用123去初始化它。
3、null传播。两个non-nullable的类型s和t,s?和t?之间的类型转换就是将底层的s和t进行转换,在将它们分别装到s?和t?中。从s到t?和从t到s?也一样。但是,将s?转换到t,或者t?转换到s是就会存在null传播问题:此时相当于将null赋值给non-nullable的值类型,.NET Framework将会抛出一个异常,如nullable中Value属性所示。即:
int? a = null;
int b = (int)a; // Exception will be thrown.
4、提升的类型转换。也就是说,本来用于non-nullable的转换操作被提升到nullable的转换操作,对应nullable的类型转换。
5、提升的操作符。首先说说非比较操作符。本来用于non-nullable的操作被提升到nullable的操作,对应nullable的操作。如:
int? a = 123;
int? b = 456;
int? x = a + b;
此处“+”操作符本来只能操作int,但是由于操作符提升,该操作被提升为两个nullable之间的加操作。根据null传播规则,若a和b中有任一个是null,那么x的结果就是null。
对于提升的比较操作符,其语义与底层类型的比较操作符一致。然而,两个nullable对象都是null,那么这两个对象是相等的。当提升的比较操作符的两个操作数其一或者两者为null,<,>,<=,>=返回的都是false。也就是说,提升的比较操作符上升到了比较nullable对象——无法判断某个值与null的大小。

说到底,将T?理解成nullable有助于理解上述规则。如果记不住上述的这些规则(以及一些此处未阐述的规则),记住这一条就行了。

PS. 笔者认为本文中介绍的技术不是很有用。

参考:
C# 2.0 / 3.0 Specification

Copyleft (C) 2007-2009 raof01.
本文可以用于除商业外的所有用途。此处“用途”包括(但不限于)拷贝/翻译(部分或全部),不包括根据本文描述来产生代码及思想。若用于非商业,请保留此 权利声明,并标明文章原始地址和作者信息;若要用于商业,请与作者联系(raof01@gmail.com),否则作者将使用法律来保证权利。
阅读(5288) | 评论(6) | 转发(0) |
给主人留下些什么吧!~~

fera2008-10-17 10:17:12

分为引用和值类型,有抄袭symbian OS之嫌。

chinaunix网友2008-09-09 22:56:41

所以说,根本就没有必要区分引用类型和值类型,整成全是引用类型,和JAVA一样,不是更好。

fera2008-08-28 12:45:33

int i = null;也能编译的话,那还要区分引用类型和值类型干嘛?

chinaunix网友2008-08-28 11:33:26

其实最完美的是改进编译器 int i=null; 也能编译过去就完美了 呸,你这是这是愚蠢行为

chinaunix网友2008-01-10 10:39:45

其实最完美的是改进编译器 int i=null; 也能编译过去就完美了