C#教程:隐式类型的局部变量
隐式类型的局部变量
在C# 1, C# 2中, 其类型系统是静态的, 显式和安全的. 在C# 3中, 这一点也几乎一致. 首先, 类型系统依然是静态和安全的(忽略显式的unsafe代码), 并且多数情况下, 它依然还是显式的——不过你也可以让编译器帮你推断局部变量的类型.
使用var声明局部变量
要使用隐式类型, 只需要简单的将原先声明的特定类型的局部变量改用var来声明, 当然会有一些限制, 我们稍候讨论, 不过基本上你只需要做类似下面这样的改变:
1: MyType variableName = someInitialValue;
改为:
1: var variableName = someInitialValue;
这两行代码实际上是一样的(基于编译后的代码), someInitialValue的类型都是MyType. 编译器在编译时可以根据初始化表达式判断类型并使得变量也拥有该类型. 其类型可以是任意的.NET类型, 包括泛型, 代理, 和接口. 变量依然还是静态的, 只是你没有将类型名写在你的代码中而已.
1: var stringVariable = "Hello, world.";
2: stringVariable = 0;
以上的代码不能编译通过, 因为stringVariable的类型是System.String, 而我们不能将值0赋给一个字符类型的变量. 在很多动态语言中, 上面的代码是可以编译通过的, 它们不会给予变量特定的类型, 相反这是编译器, IDE或者运行时环境要考虑的. 使用var并不像使用COM或者VB6中的Variant. var声明的变量类型依然是静态的; 只不过类型是由编译器推断的而已.
在VS2008中, 当你的鼠标停留在var所声明的变量上的时候, 其智能提示可以告诉你该类型的精确类型, 这与我们声明显式类型的时候是一样的。这也更清晰的表明了var声明的变量类型是静态的——编译器清楚的知道变量类型.
隐式类型的限制
你不能在所有的情况下对每一个变量都使用隐式类型声明. 只有当在下列情况之一时使用:
- 变量被声明为局部变量
- 变量作为声明的一部分被初始化
- 初始化表达式不是一个方法组或者匿名函数(没有类型转换)
- 初始化表达式不能是null
- 语句只包含一个变量声明
- 赋给变量的初始化表达式是个编译时类型
其中, 第3点与第4点是很有趣的, 你不能这样编写:
1: var starter = delegate() { Console.WriteLine(); }
因为编译器不知道你将要使用的类型, 然而你可以使用下面的代码:
1: var starter = (ThreadStart) delegate() { Console.WriteLine(); }
但如果你要这么做的话最好在开头空白的地方就使用显式类型声明. 同样的做法也适用于null的情况——你应该将null转换为适当的类型. 另外我们也可以使用方法调用值或者属性作为初始化表达式——指向常量或者构造器调用也是没有问题的. 例如, 可以使用下列的代码:
1: var args = Environment.CommandLine;
在这个例子中, args将会被初始化为string[]类型.实际上, 将方法调用的返回值赋给一个变量在LINQ to SQL当中几乎是最常用的一种情景. 另外, 还有一点也是值得注明一下的, 在using, for, foreach的头一个部分使用var声明是被允许的, 例如:
1: for (var i = 0; i < 10; i++)
2: using (var x = File.OpenText("test.dat"))
3: foreach (var s in Environment.CommandLine)
上述代码中的变量将会各自结束于int, StreamReader和string类型. 当然, 仅仅是被允许这么做并不意味着你就应该要这样做. 让我们来看一下支持和反对使用隐式类型声明的理由.
隐式类型声明的好与坏
使用隐式类型声明的主要理由(先排除匿名类型)是它减少了大量的代码输入, 同时屏幕上可见的代码数量也变多了:) 特别是当类型名包含泛型的时候可能会很长, 因为太长你可能会使用一行来声明变量, 另外一行用于初始化表达式. 另外的一种选择使用别名, 但那样的话为了要看到变量的真实类型你可能要经过一道长长的路途(无法在查看变量的时候就知道其真实类型). 相反, 如果使用var, 这一切都迎刃而解, 代码也更少, 编辑器也可以及时告诉你具体的变量类型.
所有的这些听起来都不错, 那么我们有什么理由来反对使用隐式类型声明呢? 可读性!其几乎是反对使用隐式类型声明最重要的一个理由. 对于没有使用显式声明的变量来说, 代码可能会更难读一点. 它打破了”我们声明的是什么类型, 那么其就应该始于对应的初始值”的思维, 这使得声明初始化完全分离了. 至于难读的程度则去取决于读者和初始化表达式所包含的代码. 如果是显式调用一个构造器, 那么这可以非常明显的知道你要创建的是什么类型. 如果是调用一个方法或者使用一个属性, 则取决于返回类型是否足够明显. 下面整数型的列子显示了推断类型比显式声明更加困难, 多快你可能区分中每一种类型所对应的真实类型?
1: var a = 2147483647;
2: var b = 2147483648;
3: var c = 4294967295;
4: var d = 4294967296;
5: var e = 9223372036854775807;
6: var f = 9223372036854775808;
答案是int, uint, long, long和ulong. 这些类型完全依赖于表达式的结果.
建议
以下是个人的一些关于使用隐式类型的建议
- 在着手C# 3项目的时候征求一下其他成员的意见
- 当存在疑议的时候, 在同一行中使用两种做法看看个人的感觉
- 除非代码的简洁性有非常大的意义, 否则使用显式类型声明, 如果对于数字类型, 不要为了省掉几个字符而使用隐式声明
- 如果要让某人一眼就能看出变量类型, 并且这是非常重要的, 使用显式类型声明
- 如果变量是被一个构造器初始化并且类型名很长(例如泛型), 考虑使用隐式类型声明
- 如果变量的精确类型并不是很重要, 而且上下文足够清晰, 使用隐式类型声明降低代码本身的重要性同时关注更高级别的代码对应要完成的工作.