【问题标题】:When should I use out parameters?什么时候应该使用 out 参数?
【发布时间】:2010-11-13 06:38:36
【问题描述】:

我不明白什么时候应该使用输出参数,如果我需要返回多个类型,我个人将结果包装在一个新类型中,我发现使用起来比 out 容易得多。

我见过这样的方法,

   public void Do(int arg1, int arg2, out int result)

在任何情况下这实际上是有意义的吗?

TryParse 怎么样,为什么不返回 ParseResult 类型?还是在较新的框架中返回可以为空的类型?

【问题讨论】:

  • 如果您不喜欢 TryParse,可以编写一个包装器。
  • 顺便说一句,还有一个 Parse 方法,它没有输出参数,只是返回一个值。如果字符串不能转换为类型,则抛出异常。
  • 在使用单个 out 参数时返回类型为 void 没有意义,不。但是,请参阅一些有用的答案。
  • 连微软自己都建议不要使用out参数。 msdn.microsoft.com/en-us/library/ms182131.aspx 归结为,out 是一种返回多个值而不仅仅是一个值的方法。但是,函数返回一个结构或类,其中包含您需要返回的所有内容,这样会更简洁、更模块化。

标签: c# .net out


【解决方案1】:

当你有一个TryNNN 函数时,Out 很好,很明显,即使函数不成功,out-parameter 也将始终设置。这允许您依赖于您声明的局部变量将被设置这一事实,而不必稍后在您的代码中对 null 进行检查。 (下面的注释表明该参数可以设置为null,因此您可能需要验证您正在调用的函数的文档以确定是否是这种情况。)它使代码更清晰一点并且更容易阅读。另一种情况是当您需要根据方法的条件返回一些数据和状态时,例如:

public bool DoSomething(int arg1, out string result);

在这种情况下,return 可以指示函数是否成功并且结果存储在 out 参数中。诚然,此示例是人为设计的,因为您可以设计一种方法,使函数仅返回 string,但您明白了。

一个缺点是你必须声明一个局部变量才能使用它们:

string result;
if (DoSomething(5, out result))
    UpdateWithResult(result);

代替:

UpdateWithResult(DoSomething(5));

但是,这甚至可能不是缺点,这取决于您要进行的设计。在 DateTime 的情况下,提供了两种方法(Parse 和 TryParse)。

【讨论】:

  • 实际上,在调用“失败”后期望 out 参数保持不变的情况恰恰相反。在大多数情况下,out 参数在传递给函数之前不得初始化。
  • " 如果函数不成功,out-parameter 不会改变。" - 除非函数抛出异常,否则 out 参数将始终更改。如果具有 out 参数的方法的执行路径没有为 out 参数分配值,则该方法将无法编译。因此初始化'string result = "";'在你的第二个例子中是不必要的。
  • 塞缪尔说了什么。在退出方法之前设置 out 参数是必需的。如果您希望某个值在某些情况下保持不变,请使用 ref 而不是 out。
  • out 参数只是让调用者不必初始化变量。方法本身仍然可以将其初始化为 null 或任何默认值,然后以这种方式返回。
  • 现在您不再需要声明局部变量了:if (DoSomething(5, out var result) { ... }.
【解决方案2】:

我认为 out 对于需要返回布尔值和值的情况(例如 TryParse)很有用,但如果编译器允许这样的事情会很好:

bool isValid = int.TryParse("100", out int result = 0);

【讨论】:

  • 我喜欢内联声明,我从没想过这样做
  • 现在可以在 C# 7 中使用
【解决方案3】:

和大多数事情一样,这取决于。 让我们看看选项

  • 你可以返回任何你想要的作为函数的返回值
  • 如果你想返回多个值或者函数已经有一个返回值,你可以使用 out params 或者创建一个新的复合类型,将所有这些值作为属性公开

在 TryParse 的情况下,使用 out 参数是有效的 - 您不必创建一个新的类型,它会产生 16B 的开销(在 32b 机器上)或产生在调用后让它们被垃圾收集的性能成本。例如,可以从循环中调用 TryParse - 所以这里的参数规则。
对于不会在循环中调用的函数(即性能不是主要问题),返回单个复合对象可能更“干净”(对于旁观者来说是主观的)。现在有了匿名类型和动态类型,它可能会变得更加容易。

注意:

  1. out 参数有一些需要遵循的规则,即编译器将确保函数在退出之前初始化值。因此,即使解析操作失败,TryParse 也必须将 out 参数设置为某个值
  2. TryXXX 模式是何时使用参数的一个很好的例子 - 引入 Int32.TryParse 是因为人们抱怨捕获异常以了解解析是否失败的性能命中。如果解析成功,您最有可能做的事情是获取解析后的值 - 使用输出参数意味着您不必对 Parse 进行另一个方法调用

【讨论】:

  • 我不认为有一种简单的方法可以返回匿名类型,是吗?
  • 在 C#4.0 之前,问题在于使用这些匿名类型的返回值。该方法可以将匿名对象作为“对象”返回,但是调用者不能执行 returnValue.FirstEmbeddedValue,即使它存在于对象中。需要动态方法解析才能正常工作..
  • @Gishu - 复合类型和类一样吗?
  • @RadleyAnaya - 复合 => 由多个事物/值组成。可以是 C# 类或结构。
【解决方案4】:

我知道,答案迟了几年。 如果您不希望您的方法实例化要返回的新对象,out(以及 ref)也非常有用。这在您希望为您的方法实现亚微秒级性能的高性能系统中非常重要。从内存访问的角度来看,实例化相对昂贵。

【讨论】:

    【解决方案5】:

    当然,在您发布的示例中,当您有一个需要返回多个值的方法时使用 out 参数:

    public void Do(int arg1, int arg2, out int result)
    

    使用 out 参数没有多大意义,因为您只返回一个值,如果您删除 out 参数并放入 int 返回值,则可以更好地使用该方法:

    public int Do(int arg1, int arg2)
    

    out 参数有一些好处:

    1. 输出参数最初被认为是未分配的。
      • 每个输出参数必须在方法返回之前明确赋值,如果你错过赋值,你的代码将无法编译。

    总之,我基本上尝试在我的 private API 中使用 out params 以避免创建单独的类型来包装多个返回值,并且在我的公共 API 上,我只在与TryParse 模式。

    【讨论】:

      【解决方案6】:

      是的,确实有道理。以此为例。

      String strNum = "-1";
      Int32 outNum;
      
      if (Int32.TryParse(strNum, out outNum)) {
          // success
      }
      else {
          // fail
      }
      

      如果在带有返回值的普通函数中操作失败,你会返回什么?您当然不能返回 -1 来表示失败,因为这样失败返回值和开始解析的实际值之间就没有区别了。这就是为什么我们返回一个布尔值来查看它是否成功,如果成功了,那么我们已经安全地分配了我们的“返回”值。

      【讨论】:

      • 你可以返回不存在的类型 ParsedResult : { bool Succes{get;} , T Result{get;}
      • 是的,但这需要额外的创建返回值类型的开销,如果它不是必需的。 TryParse 已经拥有此场景所需的所有功能。
      【解决方案7】:

      为返回值创建一个类型对我来说听起来并不痛苦 :-) 首先,我必须创建一个用于返回值的类型,然后在调用方法中,我将返回类型中的值分配给需要它的实际变量。

      输出参数使用起来更简单。

      【讨论】:

        【解决方案8】:

        我无法将 null 传递给 TryParse 函数的 out 参数,这让我很恼火。

        不过,在某些情况下,我更喜欢它而不是返回带有两条数据的新类型。尤其是当它们大部分不相关或片刻后只需要单个操作时。当我确实需要保存 TryParse 函数的结果值时,我真的很喜欢有一个 out 参数,而不是我必须处理的一些随机 ResultAndValue 类。

        【讨论】:

          【解决方案9】:

          如果您总是创建一个类型,那么您的应用程序中可能会出现很多混乱。

          正如这里所说,一个典型的用例是 TrySomething 方法,您希望在该方法中返回一个布尔值作为成功的指标,然后返回实际值。我还发现在 if 语句中更简洁一些 - 无论如何,所有三个选项都大致具有相同的 LOC。

          int myoutvalue;
          if(int.TryParse("213",out myoutvalue){
              DoSomethingWith(myoutvalue);
          }
          
          vs.
          
          ParseResult<int> myoutvalue = int.TryParse("213");
          if ( myoutvalue.Success ) {
              DoSomethingWith(myoutvalue.Value);
          }
          
          vs.
          
          int? myoutvalue = int.TryParse("213");
          if(myoutvalue.HasValue){
              DoSomethingWith(myoutvalue.Value);
          }
          

          至于“为什么不返回 Nullable 类型”:TryParse 从 Framework 1.x 开始就存在,而 Nullable Types 是在 2.0 中出现的(因为它们需要泛型)。那么为什么不必要地破坏兼容性或开始在某些类型上引入 TryParse 之间的不一致呢?您始终可以编写自己的扩展方法来复制已经存在的功能(请参阅 Eric Lipperts Post 关于不相关主题的内容,其中包括做/不做事情背后的一些推理)

          另一个用例是如果您必须返回多个不相关的值,即使您这样做应该会触发警报,表明您的方法可能做的太多。另一方面,如果您的方法类似于昂贵的数据库或 Web 服务调用,并且您想要缓存结果,那么这样做可能是有意义的。当然,您可以创建一个类型,但同样,这意味着您的应用程序中多了一个类型。

          【讨论】:

            【解决方案10】:

            我有时会使用 out 参数来提高可读性,因为读取方法名称比方法的输出更重要——尤其是对于除了返回结果之外还执行命令的方法。

            StatusInfo a, b, c;
            
            Initialize(out a);
            Validate(a, out b);
            Process(b, out c);
            

            对比

            StatusInfo a = Initialize();
            StatusInfo b = Validate(a);
            StatusInfo c = Process(b);
            

            至少对我来说,我在扫描时非常重视每行的前几个字符。在确认声明了一些“StatusInfo”变量后,我可以很容易地判断第一个示例中发生了什么。在第二个示例中,我看到的第一件事是检索到一堆 StatusInfo。我必须再次扫描,看看这些方法可能会产生什么样的效果。

            【讨论】:

            • 其实我觉得第一个看起来很糟糕!
            • 我将名称更改为更接近真实世界代码的名称。如果这看起来仍然很糟糕,我可以挖掘一个实际的例子。
            • 在第二个中,编译器将验证您没有乱序执行它们。第一个不是这样。
            • @MitchBlevins - 如果是这种情况,那么这是一个可怕的何时使用 out 参数的例子......我认为第二个更容易阅读和理解。第一个虽然打字少
            猜你喜欢
            • 1970-01-01
            • 2010-11-10
            • 2021-11-30
            • 1970-01-01
            • 2023-04-02
            • 2011-04-15
            • 2017-04-10
            • 2012-03-19
            相关资源
            最近更新 更多