Effective-CSharp-1优先使用隐式类型的局部变量

隐式类型的局部变量是为了支持匿名类型而加入C#语言的。[1]

用 var 来声明变量而不指明其类型,可以令开发者把注意力更多的集中在名称上,从而更好的了解其含义。[2]

在很多情况下,完全可以使用 var 来声明隐式类型的局部变量,因为编译器会自动选择合适的类型。但是不能滥用这种方法,这样可能令代码难以阅读,甚至产生微妙的类型转换 BUG。[3]

局部变量的类型推断机制并不影响 C# 的静态类型检查。类型推断不等于动态类型检查。用 var 来声明的变量是强类型的,他的类型由赋值符号右侧值的类型确定。var 的意义在于,你不用把变量的类型告诉编译器,编译器会替你判断。[4]

如下情况变量类型是显而易见的 (构造函数与工厂方法[5]

1
2
var foo = new MyType();
var thing = AccountFactory.CreateSavingAccount();

如下情况变量类型没有清晰的指出[6]

1
var result = someObject.DoSomeWork(anotherParameter);

在这种情况下应当在变量声明时指定清晰的名称,尽管方法并没有指出返回类型,但这样就能让开发者推断出变量类型,例如:

1
var HighestSellingProduct = someObject.DoSomeWork(anotherParameter);

查看代码的人会根据自己的理解认定该变量类型,可能恰好与变量在运行期的真实类型相符。但编译器不会像人那样思考,而是根据声明判定其在编译期的类型。若用 var 进行声明,编译器会推断其类型,而开发者看不到编译器推断的类型。因此,他们所认定的类型可能不符,这会在代码的维护中导致错误的修改,并产生一些本来可以避免的 Bug。

如果隐式变量的局部类型是 C# 的数值类型,那么还会产生一些另外的问题,因为在使用这些数值的时候可能会进行各种形式的转换。有些转换是宽转换 (widening conversion),这种转换是安全的,例如从 float 到 double,但还有一些转换是窄转换 (narrowing conversion),这种转化会使精度下降,例如从 long 到 int。如果明确的指出数值变量的类型,那么可以更好的控制,并且编辑器也有可能将窄转换标记出来。[7]

总之,除非开发者必须看到变量的声明类型之后才能正确的理解代码的含义,否则优先考虑var来声局部变量(此处的开发者当然也包括编写代码的人本身,因为可能有查看早前写过的代码的需求)。注意上文的优先而不是总是,例如上文的转换错误问题,对int, float, double等数值型的变量,就应该指出明确类型。[8]


  1. Microsoft Learn 的隐式类型局部变量文档说明,匿名类型没有可在源代码中显式写出的类型名,因此通常必须使用 var 保存匿名类型结果;这也是 var 被引入 C# 后的重要使用场景:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/declarations#implicitly-typed-local-variables↩︎

  2. Bill Wagner 的 Effective C# (Covers C# 6.0), 3rd Edition Item 1 建议优先考虑隐式类型局部变量,但目标是提升可读性,而不是单纯少写类型名:https://www.informit.com/store/effective-c-sharp-covers-c-sharp-6.0-50-specific-ways-9780134579283↩︎

  3. Microsoft 的 C# coding conventions 建议:当变量类型能从赋值右侧明显看出时使用 var,当方法返回类型或上下文不能清楚表达类型时避免使用 var。这支撑“可以用,但不能滥用”的边界:https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions#implicitly-typed-local-variables↩︎

  4. C# 规范 §13.6.2.2 规定,隐式类型局部变量的类型由初始化表达式的编译期类型决定;Microsoft Learn 也强调 var 声明的变量仍然是强类型。dynamic 则是另一套机制,会把部分绑定推迟到运行期:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#13622-implicitly-typed-local-variable-declarationshttps://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interop/using-type-dynamic↩︎

  5. Microsoft 的 C# coding conventions 把 var customer = new Customer() 这类右侧类型明显的声明列为适合使用隐式类型的场景,因为显式类型只是在重复右侧信息:https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions#implicitly-typed-local-variables↩︎

  6. Microsoft 的 C# coding conventions 也明确提醒:不要仅靠方法名来假设返回类型;当返回类型不能从右侧表达式看清时,显式类型通常能保留更好的阅读线索:https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions#implicitly-typed-local-variables↩︎

  7. C# 类型系统文档说明,编译期类型会影响成员访问、重载解析和转换;数值字面量与数值转换场景中,var 推断出的具体类型可能影响精度、范围或重载选择,因此在数值类型承载语义时显式写出类型更稳:https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/↩︎

  8. Microsoft Learn 的 IDE0007 / IDE0008 规则把 var 与显式类型偏好拆成“内置类型、类型明显场景、其他场景”三类,可通过 .editorconfig 配置。也就是说,var 是可审查的风格偏好与可读性判断,不是语言层面的绝对规则:https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0007-ide0008↩︎