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


69

出于好奇...当我们调用返回一些值但不处理/使用它的方法时会发生什么?我们还期望有时返回的值可能会很大。价值何去何从?它甚至被创造吗?如果是,是否会发生任何性能问题或其他问题?(在这种情况下,最佳做法是什么?)

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

我的第一个想法是,它认为系统足够智能,可以看到返回值被忽略并且不会引起任何问题(只需将其释放),但是我想确定一下,并从经验更丰富的人那里听到对它的更详细的解释。这方面比我强。

Answers:


96

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

没事

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

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

这是一些代码:

    static string GetSomething()
    {
        return "Hello";
    }

    static void Method1()
    {
        string result = GetSomething();
    }

    static void Method2()
    {
        GetSomething();
    }

如果我们看一下IL:

方法1:

.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

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

在返回“非常大”的情况下,该数据已经创建,并且该方法返回对其的引用;不是数据本身。在Method1()中,将引用分配给局部变量,并且在变量超出范围(在这种情况下,方法的结束)之后,垃圾收集器将对其进行整理。在Method2()中,在从堆栈中弹出引用之后的任何时间,垃圾收集器都可以工作。

通过忽略返回值(如果确实不需要返回值),垃圾回收器可以更快地开始工作并释放已分配的所有内存。但是其中几乎没有(在这种情况下肯定是这样),但是如果使用了长期运行的方法,则挂在该数据上可能会成为问题。

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


1
感谢您所希望的一个非常好的答案!
珍妮兹

2
minor nit:在中Method1,假设不涉及调试器,则该引用将与in中的相同点有资格进行收集Method2,并假设result该方法的其余部分未使用该引用(但可能需要长时间运行)
Damien_The_Unbeliever

1
由于主要的开销是不需要的多余数据的创建,因此我将创建一个不返回大型DataTable的其他Insert方法。
Dan Diplo

1
+1:“应该执行的操作”的一个很好的示例是返回实现IDispose的某些内容。
Binary Worrier

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

但是在大多数情况下,忽略方法的返回值表示方法的作用超出了您的需要。


1
流利的接口是最终忽略返回类型的另一个示例。
亚当·霍兹沃思

9
在流利的编码样式中,您调用的方法将返回一个自引用,以便您可以链接它们。在某些时候,您将忽略返回值,因为您已完成链接。没有教任何人吮鸡蛋,只是将其添加为另一种情况:-)
Adam Houldsworth 2011年

5
@亚当:啊,我明白你的意思了。那是流利的编码风格的一个例子。我将LINQ称为流利的编码风格,但是您绝对不会忽略返回值。即使在StringBuilder返回“ this”的东西中,我通常也会调用ToString最终结果。我不记得上一次我使用流利的样式时,我依赖于副作用而最后的方法调用仍然是返回值的方法。我确定它们存在-我只是认为它们是流畅接口的子集。
乔恩·斯基特

1
@Marc:不,我根本不是JavaScript人。
乔恩·斯基特

2
@zzzBov:首先,C#和Java不是动态语言(除了C#4的dynamic关键字)。其次,问题的标题将答案的范围限​​制在C#和方法中-它涉及“ C#方法的返回值”。在C#中的非void的方法必须明确地调用返回(或抛出一个异常,在这种情况下返回值)。是的,有些收集方法等很少使用返回值。但是我支持我的观点,对于绝大多数C#方法调用,忽略返回值是一个坏主意。
乔恩·斯基特

9

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

在这种情况下,DataTable确实会执行,IDisposable因此并非全部100%罚款:

如果返回的对象实现了,IDisposable则最好将其处置,例如:

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

DataTable 确实实现了IDisposable。令我惊讶的是,没有人因为这个原因而提出这个例子。
汤姆·梅菲尔德,

@Thomas Touche,尽管公平地说,您可以反转语言“DataTable确实实现了IDisposable,因此并非全部都是100%”。
贾斯汀

我也很难相信,在回答“当我们调用一个返回一些值但不处理/使用它的方法时会发生什么”的问题时,这是唯一甚至包括文本“ IDisposable”的答案-应该是这个问题被移植到programmers.se之类的东西?
贾斯汀

4

它取决于自身的返回值。

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

例如,如果返回的值是aFileStream并且您没有关闭流,则文件可能要等到应用程序终止后才能关闭。此外,如果您的应用程序再次尝试打开文件,它可能会抛出异常,指出“文件为由另一个进程使用”。因此,您应谨慎对待这种返回的对象,不要忽略它


2

完全可以忽略返回值。

然而。恕我直言,建筑设计不好。插入方法不应返回任何内容(成功或失败时可能返回true或false除外)。如果需要获取一个新的,更新的数据集,则应该要求它,即调用其他方法来做到这一点。


如果insert方法返回新记录的标识字段(如果可能),该怎么办?
格雷厄姆

@Graham:好点。那将是我从insert方法返回的唯一内容之一。但是,我将尝试实现一种在代码中可以避免标识字段的设计。利用存储过程和健康的数据库设计,您几乎总是可以实现这一目标。
Sani Singh Huttunen

返回返回已更新实体数的Update方法怎么样?
史蒂夫·摩根

2

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

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


2

为了对事物给出不同的看法,我认为应该重新设计该方法。看一下Command-Query分离

同样,静默忽略返回值也绝不是一个好主意。代码的读者可能没有作者的原始上下文。他们可能认为他只是忘记使用它。如果返回值不重要,则最好对该决定明确:

var ignoredReturnValue = InsertIntoDB(...);

有趣的是,如果您忽略返回值,Nemerle实际上会警告您。为了不得到警告,您必须明确决定并写下:

_ = InsertIntoDB(...);

1

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

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

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


0

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


0

不需要谈论是否可以忽略返回的类型,我们始终在C#中始终这样做。您使用的许多函数都好像在返回void一样,并不是在返回void。考虑一下像Button1.Focus()这样的通用函数

您知道.Focus()函数返回布尔值吗?如果成功专注于控件,则返回true。因此,您可以这样说来进行测试:

如果(Button1.Focus == true)MessageBox.Show(“按钮成功聚焦。”); else MessageBox.Show(“抱歉,无法专注于按钮。”);

但通常情况下,您不会这样做。您只是说:Button1.Focus();

到此为止。我可以举另外一百个例子,其中我们忽略返回值,例如当函数运行时还返回对它创建的内容的引用,但是您并不关心引用,您只是希望它执行操作(或者您只是想要简单地检查是否有引用或引用是否为空)

关键是,即使您不知道,我们也始终忽略返回值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.