不处理c#方法的返回值是可以的吗?在这个例子中什么是好的实践

本文关键字:什么 方法 处理 返回值 | 更新日期: 2023-09-27 18:03:51

出于好奇…当我们调用一个方法返回一些值,但我们没有处理/使用它时,会发生什么?我们也期望有时这个返回值会非常大。价值到哪里去了?它是被创造出来的吗?如果是,是否会出现任何性能问题或其他问题?(这种情况下的最佳实践是什么?)

假设我们有一个方法,它做一些数据库操作(插入,更新),并在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???

在我的第一个想法,它认为系统足够聪明,看到返回值被忽略,不会引起任何问题(它只是被释放),但我想要确定,并听到更详细的解释,从人谁在这个领域比我更有经验。

不处理c#方法的返回值是可以的吗?在这个例子中什么是好的实践

返回值(或引用,如果是引用类型)被压入堆栈,然后再次弹出。

没有要人。

如果返回值不相关,则可以安全地执行此操作。

但要确保它不是相关的,以防万一。

下面是一些代码:

    static string GetSomething()
    {
        return "Hello";
    }
    static void Method1()
    {
        string result = GetSomething();
    }
    static void Method2()
    {
        GetSomething();
    }

如果我们看IL:

Method1:

.locals init ([0] string result)
IL_0000:  nop
IL_0001:  call       string ConsoleApplication3.Program::GetSomething()
IL_0006:  stloc.0
IL_0007:  ret

Method2:

IL_0000:  nop
IL_0001:  call       string ConsoleApplication3.Program::GetSomething()
IL_0006:  pop
IL_0007:  ret

完全相同的指令数。在Method1中,该值存储在本地字符串结果(stloc.0)中,当它超出范围时将被删除。在Method2中,pop操作只是将其从堆栈中移除。

在你返回一些"非常大"的东西的情况下,该数据已经被创建并且该方法返回对它的引用;不是数据本身。在Method1()中,引用被分配给局部变量,垃圾收集器将在变量超出作用域(在本例中是方法的结束)后对其进行清理。在Method2()中,垃圾收集器可以在引用从堆栈中弹出后的任何时间开始工作。

通过忽略返回值,如果真的不需要它,垃圾收集器可能会更快地开始工作并释放已分配的任何内存。但是其中的数据非常少(当然在这种情况下),但是对于长时间运行的方法,挂起数据可能是一个问题。

但是最重要的是要确保你忽略的返回值不是你应该采取行动的值。

编辑:稍微软化语言,并澄清。

根据我的经验,忽略返回值很少是一个好主意——至少在返回值是用来传递新信息而不仅仅是为了方便的情况下。

一个例子,我看到它是好的:

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,它指示该值是实际上是添加的,还是已经存在。有时候你就是不在乎。

但是在绝大多数情况下,忽略方法的返回值表明它所做的工作超出了您的需要。

从内存管理的角度来看,这很好-如果调用函数不使用它,它将超出作用域并被垃圾收集。

在这个特殊的例子中,DataTable实现了IDisposable,所以它不是100%的好:

如果返回的对象实现了IDisposable,那么处理它是一个好主意,例如:

using (var retVal = InsertIntoDB(...))
{
    // Could leave this empty if you wanted
}

这取决于它自己的返回值。

编译器将在调用方方法中生成该值,所以如果该值是IDispolable或暴露Close方法,或者如果它有应该释放的资源,那么您不应该忽略它并正确处理它,否则您可能会遇到问题和内存泄漏。

例如,如果返回值是FileStream,而你没有关闭流,文件可能不会关闭,直到你的应用程序被终止,更重要的是,如果你的应用程序试图再次打开文件,它可能会抛出异常,表明"文件正在被另一个进程使用"。所以你应该小心这种类型的返回对象,永远不要忽略它!

完全可以忽略返回值

然而

。建筑设计,恕我直言,并不好。插入方法不应该返回任何东西(除了在成功或失败时可能为真或假)。如果一个人需要获得一个新的、更新的数据集,那么他应该请求它,即调用一些其他方法来这样做。

返回值如果不使用将被丢弃,但会创建它。不使用它是完全合理的(尽管您应该确定这样做是正确的),但是如果它需要大量的资源来创建,那么这就是浪费。

您可能想要考虑另一个方法是否会是更好的选择,它根本不创建返回对象。

为了从不同的角度看问题,我认为应该重新设计这个方法。看看命令-查询的分离。

同样,静默地忽略返回值很少是一个好主意。代码的读者可能没有作者的原始上下文。他们可能以为他只是忘了用。如果返回值不重要,最好明确这个决定:

var ignoredReturnValue = InsertIntoDB(...);
有趣的是,如果忽略返回值,Nemerle实际上会给你一个警告。为了不收到警告,您必须明确地说明您的决定并写入:
_ = InsertIntoDB(...);

我确信这不会引起任何问题,否则c#不会是一种非常可靠的语言。

我猜编译器不够聪明来优化这个。最有可能发生的是执行函数调用内部的普通逻辑,例如创建对象并为其分配内存。如果返回了引用类型,但没有捕获,垃圾收集将再次释放内存。

正如其他人所说,从设计的角度来看,忽略返回值确实表明存在问题,很可能您应该查看返回值。

如果您的函数对其他对象(例如DB)做一些更改,我认为如果您不需要它,可以不处理返回的对象。

所有关于是否可以忽略返回类型的讨论都是不必要的,我们在c#中一直这样做。你使用的很多函数,好像它们返回的是void,实际上并没有返回void。考虑一个常见的函数,如Button1.Focus()

你知道。focus()函数返回bool值吗?如果成功聚焦于控件,则返回true。所以你可以这样测试它是否为bool值:

如果(Button1

。Focus == true)对话框。Show("Button Focused successfully.");其他的对话框。Show("对不起,不能聚焦在按钮上");

但通常情况下,你不会这样做。你只要说:

Button1.Focus ();

就完成了。我还可以举其他上百个忽略返回值的例子,比如当一个函数运行时,也会返回一个对它创建的东西的引用,但你并不关心这个引用,你只是想让它执行操作(或者你只是想检查是否有引用或是否为空)

关键是,我们总是忽略返回值,即使你不知道。