出于好奇......当我们调用一个返回某个值但我们不处理/使用它的方法时会发生什么?而且我们也期望有时这个返回值可能非常大。那个价值在哪里?甚至创造了吗?如果是,是否存在可能出现的性能问题或其他问题? (在这种情况下,最佳做法是什么?)
假设我们有一些方法可以执行一些数据库操作(插入,更新)并返回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???
我第一次认为它认为系统足够聪明,可以看到返回的值被忽略并且不会引起任何问题(它只是被释放)但是我想确定并听到来自某人的更详细的解释。在这方面比我更有经验。
答案 0 :(得分:94)
返回的值(或引用,如果它是引用类型)被压入堆栈然后再次弹出。
没什么大不了的。
如果返回值不相关,则可以安全地执行此操作。
但请确保它不相关,以防万一。
以下是一些代码:
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
方法2:
IL_0000: nop
IL_0001: call string ConsoleApplication3.Program::GetSomething()
IL_0006: pop
IL_0007: ret
完全相同的指令数量。在Method1中,该值存储在本地字符串结果(stloc.0)中,当它超出范围时将被删除。在Method2中,pop操作只是将其从堆栈中删除。
在你返回“非常大”的情况下,已经创建了该数据并且该方法返回对它的引用;不是数据本身。在Method1()中,引用被赋值给局部变量,垃圾收集器将在变量超出范围后对其进行整理(在本例中为方法的结尾)。在Method2()中,垃圾收集器可以在从堆栈弹出引用之后的任何时候开始工作。
通过忽略返回值,如果确实不需要,垃圾收集器可以更快地开始工作并释放已分配的任何内存。但其中很少(当然在这种情况下),但是使用长期运行的方法,挂起数据可能是一个问题。
但是,最重要的是要确保您忽略的返回值不是您应该采取的行动。
答案 1 :(得分:33)
我看到它没问题的一个例子:
int foo;
int.TryParse(someText, out foo);
// Keep going
如果foo
包含“0”,则someText
将为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
表示该值是实际已添加,还是已存在。有时你只是不在乎。但是在绝大多数情况下,忽略方法的返回值表明它的效果超出了你的需要。
答案 2 :(得分:9)
从内存管理的角度来看很好 - 如果调用函数不使用它,它就会超出范围并被垃圾收集。
在这种特殊情况下,DataTable
会实施IDisposable
,因此并非所有100%罚款:
如果返回的对象实现IDisposable
,那么最好将其丢弃,例如:
using (var retVal = InsertIntoDB(...))
{
// Could leave this empty if you wanted
}
答案 3 :(得分:4)
这取决于它自己的返回值。
编译器将在调用方法中生成该值,因此,如果值为IDispolable
或公开Close
方法,或者如果它具有应该释放的资源,那么您不应该忽略它并处置它是正确的,否则你可能会遇到问题和内存泄漏..
例如,如果返回的值是FileStream
并且您没有关闭流,则在您的应用程序终止之前文件可能不会关闭,如果您的应用程序尝试再次打开文件,则可能会结束,抛出异常,指示“该文件正由另一个进程使用”。所以你应该小心那种返回的对象, 永远不要忽视它 !
答案 4 :(得分:2)
忽略返回值完全没问题。
然而。建筑设计是,恕我直言,不好。 insert方法根本不应返回任何内容(除了MAYBE成功或失败时为true或false)。如果需要获得一个新的,更新的数据集,那么应该要求它,即调用其他方法来执行此操作。
答案 5 :(得分:2)
如果未使用,则抛弃返回的值,但会创建它。不使用它是完全合理的(尽管你应该知道这是正确的事情),但如果需要大量资源来创建,那么这就浪费了。
您可能想要考虑另一种方法是否是更好的选项,根本不会创建返回对象。
答案 6 :(得分:2)
为了对事物提供不同的观点,我认为应该重新设计该方法。看看Command-Query separation。
此外,静默忽略返回值并不是一个好主意。代码的读者可能没有作者的原始上下文。他们可能会认为他只是忘了使用它。如果返回值不重要,最好明确这个决定:
var ignoredReturnValue = InsertIntoDB(...);
有趣的是,如果忽略返回值,Nemerle实际上会给出警告。为了不得到警告,你必须明确你的决定并写下:
_ = InsertIntoDB(...);
答案 7 :(得分:1)
我确信这不会导致任何问题,否则C#将不是一种非常可靠的语言。
我猜测编译器不够聪明,不能优化它。最可能发生的是执行函数调用内部的普通逻辑,例如,创建对象并为其分配内存。如果返回引用类型但未捕获,则垃圾收集将再次释放内存。
正如其他人所说,从设计的角度来看,忽略返回值确实表明存在问题,而且很可能你应该看看返回值。
答案 8 :(得分:0)
如果你的函数对其他对象做了一些更改(例如DB),我认为如果你不需要它就可以不处理返回的对象。
答案 9 :(得分:0)
所有这些谈论是否可以忽略返回的类型是不需要的,我们总是在C#中一直这样做。您使用的许多函数就像它们返回void一样不会返回void。想想像Button1.Focus()
这样的常用函数你知道.Focus()函数返回一个bool值吗?如果成功专注于控件,则返回true。所以你可以通过说:
来测试它作为一个boolif(Button1.Focus == true) MessageBox.Show(&#34; Button Focused successfully。&#34;); 其他 MessageBox.Show(&#34;无法关注按钮,抱歉。&#34;);
但通常情况下,你不会这样做。你只是说: Button1.Focus();
你完成了。我可以给出其他一些我们忽略返回值的例子,比如当一个函数运行但是也返回对它创建的东西的引用,但是你不关心引用,你只是想让它做这个动作(或者你只想简单地检查是否有引用或者是否为null)
关键是,我们始终忽略返回值,即使您不知道它。