不处理 C# 方法的返回值可以吗?这个例子中有啥好的做法?
Posted
技术标签:
【中文标题】不处理 C# 方法的返回值可以吗?这个例子中有啥好的做法?【英文标题】:Is it OK not to handle returned value of a C# method? What is good practice in this example?不处理 C# 方法的返回值可以吗?这个例子中有什么好的做法? 【发布时间】:2011-10-16 00:49:28 【问题描述】:出于好奇......当我们调用一个返回一些值但我们不处理/使用它的方法时会发生什么?而且我们还期望有时这个返回值可能非常大。那个价值去哪儿了?它甚至被创建了吗?如果是,是否存在任何性能问题或可能发生的其他问题? (这种情况下的最佳做法是什么?)
假设我们有一些方法可以执行一些数据库操作(插入、更新)并在 DataTable 对象中返回一些数据。而且我也知道这个 DataTable 对象有时会很大:
public static Datatable InsertIntoDB(...)
// executing db command, getting values, creating & returning Datatable object...
...
return myDataTable;
然后当使用这个方法时,它会被这样调用:
DataTable myDataTable = InsertIntoDB(...);
// this Datatable object is handled in some way
但有时只是这样:
InsertIntoDB(...);
// returned value not handled; Problem???
在我的第一个想法中,它认为系统足够聪明,可以看到返回的值被忽略并且不会引起任何问题(它只是简单地发布了),但我想确定并从某个人那里听到更详细的解释在这方面比我更有经验。
【问题讨论】:
【参考方案1】:所有这些关于是否可以忽略返回类型的讨论都是不必要的,无论如何我们在 C# 中一直这样做。您使用的许多函数好像它们返回 void 并没有返回 void。想想像 Button1.Focus() 这样的通用函数
你知道 .Focus() 函数返回一个布尔值吗?如果成功聚焦于控件,则返回 true。因此,您可以通过以下方式将其作为布尔值进行测试:
如果 (Button1.Focus == true) MessageBox.Show("按钮聚焦成功。"); 别的 MessageBox.Show("无法关注按钮,抱歉。");
但通常情况下,您不会这样做。你只是说: Button1.Focus();
你就完成了。我可以给出一百个我们忽略返回值的例子,比如当一个函数运行但也返回一个它创建的东西的引用,但你不关心引用,你只是想让它做动作(或者你只是想简单地检查是否有引用或是否为空)
关键是,我们一直忽略返回值,即使你不知道。
【讨论】:
【参考方案2】:为了对事物给出不同的看法,我认为应该重新设计该方法。看看Command-Query separation。
此外,默默地忽略返回值并不是一个好主意。代码的读者可能没有作者的原始上下文。他们可能认为他只是忘了使用它。如果返回值不重要,最好明确说明这个决定:
var ignoredReturnValue = InsertIntoDB(...);
有趣的是,如果您忽略返回值,Nemerle 实际上会给您一个警告。为了不收到警告,您必须明确说明您的决定并写下:
_ = InsertIntoDB(...);
【讨论】:
【参考方案3】:编辑:稍微软化了语言,并澄清了。
根据我的经验,很少忽略返回值是个好主意 - 至少在返回值用于传达新信息而不是为了方便的情况下。
我见过的一个例子:
int foo;
int.TryParse(someText, out foo);
// Keep going
如果someText
包含“0”或无法解析,则此处foo
将为0。 我们可能不在乎是哪种情况在哪种情况下方法的返回值与我们无关。
另一个例子是在字典中 - 假设您正在尝试计算每个字符串的出现次数。您可以使用:
int count;
dictionary.TryGetValue(word, out count);
dictionary[word] = count + 1;
如果字典中没有该单词开头,则相当于计数为 0 - 这是调用 TryGetValue
的结果已经发生的情况。
作为一个反例,忽略 Stream.Read
返回的值(并假设它已设法读取所有您要求的数据)是一个常见的错误。
如果您不需要返回值并且需要花费大量精力进行计算,那么可能值得寻找能够在没有额外计算的情况下实现相同预期副作用的东西 - 但没有额外的性能含义。我更担心忽略返回值的正确性而不是性能。
编辑:可以忽略返回值的其他示例:
一些流畅的接口,包括StringBuilder
;虽然StringBuilder.Append(x).Append(y);
在第二次调用中使用第一个返回值,但调用的返回值通常会被忽略,例如在循环中追加时
某些集合调用可能会提供有时会被忽略的返回值 - 例如HashSet<T>.Add
指示该值是实际添加还是已经存在。有时你就是不在乎。
但在绝大多数情况下,忽略方法的返回值表明它做的比你需要的多。
【讨论】:
Fluent 接口是另一个最终忽略返回类型的例子。 在流畅的编码风格中,您调用的方法会返回一个自引用,以便您可以链接它们。在某些时候,您会忽略返回值,因为您已完成链接。不教任何人吸鸡蛋,只是将其添加为另一种情况:-) @Adam:啊,我明白你的意思了。这是流畅编码风格的一个示例。我将 LINQ 称为流利的编码风格,但您绝对不要忽略返回值。即使在像StringBuilder
这样返回“this”的情况下,我通常也会在最终结果上调用ToString
。我不记得上一次使用流畅的样式是什么时候了,我依赖副作用并且最终的方法调用仍然是返回值的方法。我确定它们存在 - 我只是认为它们是流畅界面的子集。
@Marc:不,我根本不是 javascript 人。
@zzzBov:首先,C# 和 Java 不是动态语言(除了 C# 4 的 dynamic 关键字)。其次,问题的标题将这个答案的范围限制为 C# 和 methods - 它是关于“C# 方法的返回值”。 C# do 中的非 void 方法必须显式调用 return(或抛出异常,在这种情况下返回值)。是的,有一些收集方法等,其中返回值相对很少使用。但我坚持我的观点,对于 vast 的大多数 C# 方法调用,忽略返回值是个坏主意。【参考方案4】:
从内存管理的角度来看,这很好 - 如果调用函数不使用它,它就会超出范围并被垃圾回收。
在这种特殊情况下,DataTable
确实实现了IDisposable
,所以它并不是 100% 没问题:
如果返回的对象实现了IDisposable
,那么处置它是个好主意,例如:
using (var retVal = InsertIntoDB(...))
// Could leave this empty if you wanted
【讨论】:
DataTable
确实实现IDisposable
。我很惊讶没有人因为这个原因而举出这个例子。
@Thomas Touche,但公平地说,您可以将语言颠倒过来“DataTable
确实实现了IDisposable
,所以它并不是 100%。”
我也很难相信,在回答“当我们调用一个返回一些值但我们不处理/使用它的方法时会发生什么”这个问题时,这是 only 答案甚至包含文本“IDisposable”——这个问题应该迁移到programmers.se 还是什么的?【参考方案5】:
返回的值(或引用,如果是引用类型)被压入堆栈,然后再次弹出。
没什么大不了的。
如果返回值不相关,您可以安全地执行此操作。
但请确保它不相关,以防万一。
这里有一些代码:
static string GetSomething()
return "Hello";
static void Method1()
string result = GetSomething();
static void Method2()
GetSomething();
如果我们看一下 IL:
方法一:
.locals init ([0] string result)
IL_0000: nop
IL_0001: call string ConsoleApplication3.Program::GetSomething()
IL_0006: stloc.0
IL_0007: ret
方法二:
IL_0000: nop
IL_0001: call string ConsoleApplication3.Program::GetSomething()
IL_0006: pop
IL_0007: ret
指令数量完全相同。在 Method1 中,该值存储在本地字符串结果 (stloc.0) 中,超出范围时将被删除。在 Method2 中,pop 操作只是将其从堆栈中移除。
在您返回“非常大”的内容的情况下,该数据已经创建并且该方法返回对它的引用;而不是数据本身。在 Method1() 中,将引用分配给局部变量,并且在变量超出范围(在本例中为方法结束)后,垃圾收集器将对其进行清理。在 Method2() 中,垃圾收集器可以在引用从堆栈中弹出后的任何时间开始工作。
通过忽略返回值,如果确实不需要它,垃圾收集器可能会更快地开始工作并释放任何已分配的内存。但是其中的内容很少(当然在这种情况下),但是对于长时间运行的方法,挂在这些数据上可能是个问题。
但最重要的是确保您忽略的返回值不是您应该采取行动的东西。
【讨论】:
感谢我期待的一个非常好的答案! 次要问题:在Method1
中,假设不涉及调试器,则引用将有资格在与Method2
相同的点进行收集,假设未使用result
通过方法的其余部分(无论运行时间多长)
由于主要开销是创建您不需要的多余数据,因此我会考虑创建一个不返回大型 DataTable 的不同 Insert 方法。
+1:“你应该采取行动”的一个很好的例子是任何返回实现 IDispose 的东西。【参考方案6】:
这取决于它自己的返回值。
编译器会在调用者方法中生成那个值,所以如果这个值是IDispolable
或者暴露Close
方法或者如果它有应该释放的资源,那么你不应该忽略它并正确处理它,或者否则你可能会遇到问题和内存泄漏..
例如,如果返回值为FileStream
,并且您没有关闭流,则在您的应用程序终止之前文件可能不会关闭,而且如果您的应用程序再次尝试打开文件,它可能会抛出异常表示“该文件正被另一个进程使用”。所以你应该小心那种返回的对象,永远不要忽略它!
【讨论】:
【参考方案7】:如果不使用返回值将被丢弃,但它会被创建。不使用它是完全合理的(尽管您应该确定这是正确的做法),但如果创建需要大量资源,那么这就是浪费。
您可能需要考虑另一种方法是否会是更好的选择,它根本不会创建返回对象。
【讨论】:
【参考方案8】:我确信这不会导致任何问题,否则 C# 不会是一种非常可靠的语言。
我猜编译器不够聪明,无法对此进行优化。最有可能发生的是函数调用内部的普通逻辑被执行,例如创建对象并为其分配内存。如果返回了引用类型但没有被捕获,垃圾回收会再次释放内存。
正如其他人所说,从设计视图中忽略返回值确实表明存在问题,您很可能应该查看返回值。
【讨论】:
【参考方案9】:忽略返回值完全没问题。
但是。建筑设计,恕我直言,不好。插入方法根本不应该返回任何东西(除了 MAYBE true 或 false 成功或失败)。如果需要获得一个新的、更新的数据集,那么应该要求它,即调用其他方法来这样做。
【讨论】:
insert方法返回新记录的identity字段怎么样(如果可能的话)? @Graham:好点。这将是我从插入方法返回的唯一内容之一。但是,我会尝试在代码中实现可以避免标识字段的设计。利用存储过程和健康的数据库设计,您几乎总能实现这一目标。 如何使用 Update 方法返回已更新实体的数量?【参考方案10】:如果您的函数对其他对象(例如数据库)进行了一些更改,我认为如果您不需要返回的对象,则可以不处理它。
【讨论】:
以上是关于不处理 C# 方法的返回值可以吗?这个例子中有啥好的做法?的主要内容,如果未能解决你的问题,请参考以下文章