可空值类型
作者:追风剑情 发布于:2021-1-18 10:47 分类:C#
我们知道值类型的变量永远不会为 null:它总是包含值类型的值本身。事实上,这正是“值类型”一词的由来。遗憾的是,这在某些情况下会成为问题。例如,设计数据库时,可将一个列的数据类型定义成一个 32 位整数,并映射到 FCL(Framework Class Library)的 Int32数据类型。但是,数据库中的一个列可能允许值为空;也就是说,该列在某一行上允许没有任何值。用 Microsoft.NET Framework 处理数据库数据可能变得很困难,因为在 CLR 中,没有办法将 Int32 值表示成 null。
下面是另一个例子:Java 的 java.util.Date 类是引用类型,所以该类型的变量能设为 null但CLR 的 System.DateTime 是值类型,DateTime 变量永远不能设为 null。如果用 Java写的一个应用程序想和运行 CLR的 Web 服务交流日期/时间,那么一旦 Java 程序发送 null就会出问题,因为 CLR 不知道如何表示 null,也不知道如何操作它。
为了解决这个问题,Microsoft 在 CLR 中引入了可空值类型的概念。为了理解它们是如何工作的,先来看看 FCL 中定义的 System,Nullable<T>结构。以下是 System.Nullablee<T>定义的逻辑表示:
//StructLayout在System.Runtime.InteropServices中 [Serializable, StructLayout(LayoutKind.Sequential)] public struct Nullable<T> where T : struct { //这两个字段表示状态 private Boolean hasValue = false;//假定null internal T value = default(T); //假定所有位都为零 public Nullable(T value) { this.value = value; this.hasValue = true; } public Boolean HasValue { get { return hasValue; } } public T Value { get { if (!hasValue) { throw new InvalidOperationException( "Nullable object must have a value."); } return value; } } public T GetValueOrDefault() { return value; } public T GetValueOrDefault(T defaultValue) { if (!HasValue) return defaultValue; return value; } public override bool Equals(object other) { if (!HasValue) return (other == null); if (other == null) return false; return value.Equals(other); } public override int GetHashCode() { if (!HasValue) return 0; return value.GetHashCode(); } public override string ToString() { if (!HasValue) return ""; return value.ToString(); } //隐式转换 T到Nullable<T> public static implicit operator Nullable<T>(T value) { return new Nullable<T>(value); } //注意:可能引发错误的转换要让程序员显示转换 //显式转换 Nullable<T>到T public static explicit operator T(Nullable<T> value) { return value.Value; } }
C#允许使用相当简单的语法初始化上述两个Nullable<Int32>变量x和y。事实上,C#开发团队的目的是将可空值类型集成到C#语言中,使之成为“一等公民”。为此,C#提供了更清晰的语法来处理可空值类型。C#允许用问号表示法来声明并初始化x和y变量:
Int32? x = 5;
Int32? y = null;
在C#中,Int32? 等价于 Nullable<Int32>。但C#在此基础上更进一步,允许开发人员在可空实例上执行转换和转型。C#还允许向可空实例应用操作符。
译注:作者在这里区分了转换和转型。例如,从Int32的可空版本到非空版本(或相反),称为“转换”。但是,涉及不同基元类型的转换,就称为“转型”或"强制类型转换"。
下面总结了C#如何解析操作符:
● 一元操作符(+, ++, -, --, !, ~)
操作数是null,结果就是null。
● 二元操作符(+,-,*,/,%,&,|,^,<<,>>)
两个操作数任何一个是null,结果就是null。但有一个例外,它发生在将&和|操作符应用于Boolean?操作数的时候。在这种情况下,两个操作符的行为和SQL的三值逻辑一样。对于这两个操作符,如果两个操作数都不是null,那么操作符和平常一样工作。如果两个操作数都是null,结果就是null。特殊行为仅在其中之一为null时发生。下表列出了针对操作数的true,false和null三个值的各种组合,两个操作符的求值情况。
操作数1→ 操作数2↓ |
true | false | null |
true |
&=true |=true |
&=false |=true |
&=null |=true |
false |
&=false |=true |
&=false |=false |
&=false |=null |
null |
&=null |=true |
&=false |=null |
&=null |=null |
● 相等性操作符(==, !=)
两个操作数都是null,两者相等。一个操作数是null,两者不相等。两个操作数都不是null,就比较值来判断是否相等。
● 关系操作符(<,>,<=,>=)
两个操作数任何一个是null,结果就是false。两个操作数都不是null,就比较值。
注意,操作可空实例会生成大量代码。而且操作可空类型的速度慢于非可空类型。
可定义自己的值类型来重载各种操作符。使用自己的值类型的可空实例,编译器能正确识别它并调用你重载的操作符(方法)。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp19 { class Program { static void Main(string[] args) { Point? p1 = new Point(1,1); Point? p2 = new Point(2,2); Console.WriteLine("Are points equal? " + (p1 == p2).ToString()); Console.WriteLine("Are points not equal? " + (p1 != p2).ToString()); } private static void ConversionsAndCasting() { //从非可空Int32隐式转换为Nullable<Int32> Int32? a = 5; //从null隐式转换为Nullable<Int32> Int32? b = null; //从Nullable<Int32>显式转换为非可空Int32 Int32 c = (Int32)a; //在可空基元类型之间转型 Double? d = 5; //Int32转型为Double?(d是double值5.0) Double? e = b; //Int32?转型为Double?(e是null) } //C#还允许向可空实例应用操作符 private static void Operators() { Int32? a = 5; Int32? b = null; //一元操作符 (+ ++ - -- ! ~) a++; //a=6 b = -b;//b=null //二元操作符 (+ - * / % & | ^ << >>) a = a + 3;//a = 9 b = b * 3;//b = null //相等性操作符(== !=) if (a == null) { } else { } if (b == null) { } else { } if (a != b) { } else { } //比较操作符(< > <= >=) if (a < b) { } else { } } //注意,操作可空实例会生成大量IL代码,而且操作可空类型的速度慢于非可空类型。 //例如以下方法 //private static Int32? NullableCodeSize(Int32? a, Int32? b) //{ // return (a + b); //} //编译器生成的IL代码等价于以下C#代码: private static Nullable<Int32> NullableCodeSize ( Nullable<Int32> a, Nullable<Int32> b) { Nullable<Int32> nullable1 = a; Nullable<Int32> nullable2 = b; if (!(nullable1.HasValue & nullable2.HasValue)) return new Nullable<Int32>(); return new Nullable<Int32>( nullable1.GetValueOrDefault() + nullable2.GetValueOrDefault()); } internal struct Point { private Int32 m_x, m_y; public Point(Int32 x, Int32 y) { m_x = x; m_y = y; } public static Boolean operator==(Point p1, Point p2) { return (p1.m_x == p2.m_x) && (p1.m_y == p2.m_y); } public static Boolean operator!=(Point p1, Point p2) { return !(p1 == p2); } } } }
C#提供了一个“空接合操作符”(null-coalescing operator),即??操作符,它要获取两个操作数。假如左边的操作数不为null,就返回这个操作数的值。如果左边的操作数为null,就返回右边的操作数的值。利用空接合操作符,可以方便地设置变量的默认值。
空接合操作符的一个好处在于,它既能用于引用类型,也能用于可空值类型。以下代码演示了如何使用??操作符:
private static void NullCoalescingOperator() { Int32? b = null; //下面这行等于: //x = (b.HasValue) ? b.Value : 123 Int32 x = b ?? 123; Console.WriteLine(x); // "123" //下面这行等于: //String temp = GetFilename(); //filename = (temp != null) ? temp : "Untitled"; String filename = GetFilename() ?? "Untitled"; }
有人争辩说??操作符不过是?:操作符的“语法糖”而已,所以C#编译器团队不应该将这个操作符添加到语言中。实际上,??提供了重大的语法上的改进。
第一个改进是??操作符能更好的支持表达式:
Func<String> f = () => SomeMethod() ?? "Untitled";
相比下一行代码,上术代码更容易阅读和理解。下面这行代码要求进行变量赋值,而且用一个语句还搞不定:
Func<String> f = () => { var temp = SomeMethod();
return temp != null ? temp : "Untitled";};
第二个改进是??在复合情形中更好用。例如,下面这行代码:
String s = SomeMethod1() ?? SomeMethod2() ?? "Untitled";
它比下面这地堆代码更容易阅读和理解:
String s; var sm1 = SomeMethod1(); if (sm1 != null) s = sm1; else { var sm2 = SomeMethod2(); if (sm2 != null) s = sm2; else s = "Untitled"; }
CLR 内建对可空值类型的支持。这个特殊的支持是针对装箱、拆箱、调用 GetType 和调用 接口方法提供的,它使可空类型能无缝地集成到 CLR 中,而且使它们具有更自然的行为, 更符合大多数开发人员的预期。下面深入研究一下 CLR 对可空类型的特殊支持。
1、可空值类型的装箱
假定有一个逻辑上设为 null 的 Nullable
具体地说,当 CLR 对 Nullable<T>实例进行装箱时,会检查它是否为 null。如果是,CLR不装箱任何东西,直接返回 null。如果可空实例不为 null,CLR 从可空实例中取出值并进行装箱。也就是说,一个值为 5 的 Nullable<Int32>会装箱成值为 5 的已装箱 Int32。以下代码演示了这一行为:
//对Nullable进行装箱,要么返回 null,要么返回一个已装箱的T Int32? n= null; Object о= n;//o为nu11 Console.Writeline("o is null={0}", o == null);//"True" n = 5; o = n;//o引用一个已装箱的Int32 console.Writeline("o's type={0}", o.GetType());//"System.Int32"
2、可空值类型的拆箱
CLR允许将已装箱的值类型T拆箱为一个T或者Nullable
//创建已装箱的Int32 Object o = 5; //把它拆箱为一个Nullable和一个Int32 Int32? a = (Int32?)o; //a=5 Int32 b = (Int32)o; //b=5 //创建初始化为null的一个引用 o = null; //把它“拆箱”为一个Nullable 和一个Int32 a = (Int32?)o; //a=null b = (Int32)o; //NullReferenceException
3、通过可空值类型调用GetType
在Nullable<T>对象上调用GetType,CLR实际会“撒谎”说类型是T,而不是Nullable<T>。以下代码演示了这一行为:
Int32? x = 5;
//下面这行会显示“System.Int32”,而非"System.Nullable<Int32>"
Console.WriteLine(x.GetType());
4、通过可空值类型调用接口方法
以下代码将 Nullable<Int32>类型的变量 n 转型为接口类型 IComparable<Int32>。Nullable<T>不像 Int32 那样实现了 IComparable<Int32>接口,但C#编译器允许这样的代码通过编译,而且 CLR 的校验器也认为这样的代码可验证,从而允许使用更简洁的语法:
Int32? n = 5; Int32 result =((IComparable) n).CompareTo(5);//能顺利编译和运行 Console.WriteLine(result); // 0
假如 CLR不提供这一特殊支持,要在可空值类型上调用接口方法,就必须写很繁琐的代码。首先要转型为已拆箱的值类型,然后才能转型为接口以发出调用:
Int32 result =((IComparable)(Int32) n).CompareTo(5);// 很繁琐
标签: C#
日历
最新文章
随机文章
热门文章
分类
存档
- 2024年11月(3)
- 2024年10月(5)
- 2024年9月(3)
- 2024年8月(3)
- 2024年7月(11)
- 2024年6月(3)
- 2024年5月(9)
- 2024年4月(10)
- 2024年3月(11)
- 2024年2月(24)
- 2024年1月(12)
- 2023年12月(3)
- 2023年11月(9)
- 2023年10月(7)
- 2023年9月(2)
- 2023年8月(7)
- 2023年7月(9)
- 2023年6月(6)
- 2023年5月(7)
- 2023年4月(11)
- 2023年3月(6)
- 2023年2月(11)
- 2023年1月(8)
- 2022年12月(2)
- 2022年11月(4)
- 2022年10月(10)
- 2022年9月(2)
- 2022年8月(13)
- 2022年7月(7)
- 2022年6月(11)
- 2022年5月(18)
- 2022年4月(29)
- 2022年3月(5)
- 2022年2月(6)
- 2022年1月(8)
- 2021年12月(5)
- 2021年11月(3)
- 2021年10月(4)
- 2021年9月(9)
- 2021年8月(14)
- 2021年7月(8)
- 2021年6月(5)
- 2021年5月(2)
- 2021年4月(3)
- 2021年3月(7)
- 2021年2月(2)
- 2021年1月(8)
- 2020年12月(7)
- 2020年11月(2)
- 2020年10月(6)
- 2020年9月(9)
- 2020年8月(10)
- 2020年7月(9)
- 2020年6月(18)
- 2020年5月(4)
- 2020年4月(25)
- 2020年3月(38)
- 2020年1月(21)
- 2019年12月(13)
- 2019年11月(29)
- 2019年10月(44)
- 2019年9月(17)
- 2019年8月(18)
- 2019年7月(25)
- 2019年6月(25)
- 2019年5月(17)
- 2019年4月(10)
- 2019年3月(36)
- 2019年2月(35)
- 2019年1月(28)
- 2018年12月(30)
- 2018年11月(22)
- 2018年10月(4)
- 2018年9月(7)
- 2018年8月(13)
- 2018年7月(13)
- 2018年6月(6)
- 2018年5月(5)
- 2018年4月(13)
- 2018年3月(5)
- 2018年2月(3)
- 2018年1月(8)
- 2017年12月(35)
- 2017年11月(17)
- 2017年10月(16)
- 2017年9月(17)
- 2017年8月(20)
- 2017年7月(34)
- 2017年6月(17)
- 2017年5月(15)
- 2017年4月(32)
- 2017年3月(8)
- 2017年2月(2)
- 2017年1月(5)
- 2016年12月(14)
- 2016年11月(26)
- 2016年10月(12)
- 2016年9月(25)
- 2016年8月(32)
- 2016年7月(14)
- 2016年6月(21)
- 2016年5月(17)
- 2016年4月(13)
- 2016年3月(8)
- 2016年2月(8)
- 2016年1月(18)
- 2015年12月(13)
- 2015年11月(15)
- 2015年10月(12)
- 2015年9月(18)
- 2015年8月(21)
- 2015年7月(35)
- 2015年6月(13)
- 2015年5月(9)
- 2015年4月(4)
- 2015年3月(5)
- 2015年2月(4)
- 2015年1月(13)
- 2014年12月(7)
- 2014年11月(5)
- 2014年10月(4)
- 2014年9月(8)
- 2014年8月(16)
- 2014年7月(26)
- 2014年6月(22)
- 2014年5月(28)
- 2014年4月(15)
友情链接
- Unity官网
- Unity圣典
- Unity在线手册
- Unity中文手册(圣典)
- Unity官方中文论坛
- Unity游戏蛮牛用户文档
- Unity下载存档
- Unity引擎源码下载
- Unity服务
- Unity Ads
- wiki.unity3d
- Visual Studio Code官网
- SenseAR开发文档
- MSDN
- C# 参考
- C# 编程指南
- .NET Framework类库
- .NET 文档
- .NET 开发
- WPF官方文档
- uLua
- xLua
- SharpZipLib
- Protobuf-net
- Protobuf.js
- OpenSSL
- OPEN CASCADE
- JSON
- MessagePack
- C在线工具
- 游戏蛮牛
- GreenVPN
- 聚合数据
- 热云
- 融云
- 腾讯云
- 腾讯开放平台
- 腾讯游戏服务
- 腾讯游戏开发者平台
- 腾讯课堂
- 微信开放平台
- 腾讯实时音视频
- 腾讯即时通信IM
- 微信公众平台技术文档
- 白鹭引擎官网
- 白鹭引擎开放平台
- 白鹭引擎开发文档
- FairyGUI编辑器
- PureMVC-TypeScript
- 讯飞开放平台
- 亲加通讯云
- Cygwin
- Mono开发者联盟
- Scut游戏服务器引擎
- KBEngine游戏服务器引擎
- Photon游戏服务器引擎
- 码云
- SharpSvn
- 腾讯bugly
- 4399原创平台
- 开源中国
- Firebase
- Firebase-Admob-Unity
- google-services-unity
- Firebase SDK for Unity
- Google-Firebase-SDK
- AppsFlyer SDK
- android-repository
- CQASO
- Facebook开发者平台
- gradle下载
- GradleBuildTool下载
- Android Developers
- Google中国开发者
- AndroidDevTools
- Android社区
- Android开发工具
- Google Play Games Services
- Google商店
- Google APIs for Android
- 金钱豹VPN
- TouchSense SDK
- MakeHuman
- Online RSA Key Converter
- Windows UWP应用
- Visual Studio For Unity
- Open CASCADE Technology
- 慕课网
- 阿里云服务器ECS
- 在线免费文字转语音系统
- AI Studio
- 网云穿
- 百度网盘开放平台
- 迅捷画图
- 菜鸟工具
- [CSDN] 程序员研修院
- 华为人脸识别
- 百度AR导航导览SDK
- 海康威视官网
- 海康开放平台
- 海康SDK下载
- git download
交流QQ群
-
Flash游戏设计: 86184192
Unity游戏设计: 171855449
游戏设计订阅号