为什么要在一行中声明变量,然后在下一行进行赋值?

5

我经常在C#代码中看到以下惯例:

some_type val;
val = something;

喜欢

DataTable dt;
dt = some_function_returning_datatable();

或者

DataTable dt = new DataTable();
dt = some_function_returning_datatable();

替代

some_type val = something;
DataTable dt = some_function_returning_datatable();

我最初认为这是一种习惯,留下了从前必须在作用域顶部声明所有局部变量的日子。但我已经学会不要轻易忽略资深开发人员的习惯。

(在我的第三个代码段中,当我们首先使用new然后再从函数中赋值给dt时,这不会浪费内存吗?)

那么,声明一个变量并在之后赋值有什么好处呢?

5个回答

18

在我的第三段代码中,当我们先使用new为dt分配内存,然后再从函数中赋值时,这不会浪费内存吗?

确实会浪费。虽然相对较小——创建一个没用的DataTable对象,但仍是一种浪费且不清晰的做法。

那么,声明变量和赋值合并在一行之后再进行赋值有什么好处呢?

只有在你不能立即获得值时才会这样做。例如:

string x;
if (someCondition) {
    // Do some work
    x = someResult;
} else {
    // Do some other work
    x = someOtherResult;
}

通常情况下,可以使用条件运算符或将该代码提取到方法中来改善。但有时候并不总是如此。

对于 简单 的情况:

Foo x = SomeInitializationWithNoUsefulSideEffects();
x = SomeUsefulValue();

或者

Foo x;
x = SomeUsefulValue();

你应该绝对进行重构。

Foo x = SomeUsefulValue();

另一个有趣的情况是,声明点在捕获变量时确实会产生影响,尽管通常不是预期的方式:

int x;
for (int i = 0; i < 10; i++) {
    x = SomeFunction();
    actions.Add(() => Console.WriteLine(x));
}

对比

for (int i = 0; i < 10; i++) {
    int x = SomeFunction();
    actions.Add(() => Console.WriteLine(x));
}
在第一个代码片段中,每个委托将捕获相同的变量,因此它们实际上都会看到从SomeFunction返回的最后一个值。在第二个代码片段中,每个委托将捕获单独的"实例" x

一个未分配的引用是否有任何费用? - Sleiman Jneidi
@sleimanjneidi:不太清楚你的意思。“未分配引用”不是我理解的术语。 - Jon Skeet
@JonSkeet 字符串 s; 独自存在,没有创建一个对象。 - Sleiman Jneidi
@sleimanjneidi:好的,那么这是一个变量,而不是引用。假设它在其他地方使用-因此无法完全优化掉-无论何时初始化,它将占用相同的存储空间(无论是在堆栈还是堆上)。 - Jon Skeet
5
不。投票给帖子,而不是用户。是的,它有很大可能是正确的,但要根据帖子本身的价值进行评估。 - ChrisF
是的,当人们想在 switch 语句中分配 x(使用条件运算符 ?: 不合适时),也会这样做。 - Jeppe Stig Nielsen

9
“但我已经学会不要轻易忽视资深开发者的习惯。”
“毫不犹豫地迅速摒弃它们。在C#中编写这样的代码是完全没有意义的:”
some_type val;
val = something;

改为:

some_type val = something;

或者:

DataTable dt = new DataTable();
dt = some_function_returning_datatable();

改为:

DataTable dt = some_function_returning_datatable();

无论如何,如果您在代码中不将它们解除,C#编译器在生成结果的IL时会将它们解除。

只有当变量立即被用作 ref 或 out 参数时,才需要进行单独声明的原因。 - MaLio
+1 因为这个答案是来自于 Stack Overflow 的前三名使用之一,尽管 .net 不是我的风格。 - coolguy
2
请为所呈现的内容/答案投票,而不仅仅是因为来自前十名之一的人回答了它。 - V4Vendetta

0

在阅读代码时,了解哪些变量将在其作用域内更改以及哪些变量不会更改非常有用。一种有用的约定是,如果变量的值在其作用域内保持不变,则在声明变量的同一行初始化变量;如果变量的值将在其作用域内重新分配,并且这一事实不会立即显而易见,则在与其声明不同的行上初始化它。

  1. 它的值将在其作用域内重新分配,且
  2. 该事实不会立即显而易见

关于某些事情是否“立即显而易见”,存在一些判断空间;一些情况可能符合条件:

  1. 将变量初始化为明显的常量值(特别是类型的默认值)
  2. 在定义几行之内重新分配值
  3. 变量的名称和初始化表达式暗示循环初始化(例如,“var currentFoo = firstFoo;”)

变量的作用域越长,如果值会发生变化,我就越有可能使用单独的初始化和声明。


0

这是每个人自己的意愿。没有性能问题。由于CLR会根据您的声明方式进行声明,无论您是一行还是两行。

我总是遵循

int i=5;

在可能的情况下,尽量使用。但是请确保,因为当您在单行中声明int i=5;时,CLR必须创建一个新的整数实例。

0
SomeType someVar;
someVar = new(SomeType);

并且

SomeType someVar = new(SomeType);

这些是等效的。可能是开发者的习惯,也可能是分割是必要的,但已经被重构掉了,但并不完全。

SomeType someVar = new(SomeType);
someVar = GetSomeTypeFromSomewhereElse();

在编程中,这种写法最多只能算是低效的代码,因为第一个实例看起来什么也没做就已经超出了作用域。当然,这取决于构造函数中是否有任何副作用。如果有的话,在同事审查期间我们将不得不进行长时间而痛苦的讨论。 :D

之后,正如其他人所说,这是因为必须声明someVar,以便可以通过一些进一步的逻辑进行赋值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接