关于c#:什么时候应该使用参数?

When should I use out parameters?

我不明白什么时候应该使用输出参数,如果我需要返回多个类型,我会亲自将结果包装为一个新类型,我发现使用它比使用out更容易。

我见过这样的方法,

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

有没有真正有意义的案例?

TryParse怎么样,为什么不返回ParseResult类型?或者在较新的框架中返回一个可为空的类型?


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

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

在这种情况下,返回可以指示函数是否成功,结果是否存储在out参数中。诚然,这个例子是人为设计的,因为您可以设计一种方法,在这种方法中,函数只返回一个string,但是您得到了这个想法。

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

1
2
3
string result;
if (DoSomething(5, out result))
    UpdateWithResult(result);

而不是:

1
UpdateWithResult(DoSomething(5));

然而,这可能不是一个缺点,这取决于你想要的设计。对于datetime,提供了两种方法(parse和typarse)。


以及大多数事情都要看情况而定。让我们看看选项

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

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

注:

  • out参数具有一些需要遵循的规则,即编译器将确保函数在退出前初始化该值。因此,即使解析操作失败,typarse也必须将out参数设置为某个值。
  • Tryxx模式是一个很好的例子,说明了何时使用参数-Int32.Trparse是被引入的,因为人们抱怨捕获异常以了解解析是否失败。在分析成功的情况下,最有可能做的事情就是获取分析的值-使用out参数意味着您不必再调用另一个方法来分析

  • 我认为out对于需要返回布尔值和值的情况很有用,比如typarse,但是如果编译器允许这样的情况会更好:

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


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


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

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

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

    1
    public int Do(int arg1, int arg2)

    输出参数有一些好处:

  • 输出参数最初被认为是未分配的。
    • 每个out参数都必须在方法返回之前被明确地赋值,如果您错过了赋值,代码将不会编译。
  • 总之,我基本上尝试在我的私有API中使用参数来避免创建单独的类型来包装多个返回值,在我的公共API中,我只在与台盼模式匹配的方法上使用它们。


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

    如前所述,一个典型的用例是TrySomething方法,您希望返回一个bool作为成功的指示器,然后返回实际值。我还发现,在if语句中,稍微干净一点——这三个选项大体上都有相同的loc。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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);
    }

    至于"为什么不返回一个可以为空的类型":自框架1.x以来就存在函数类型,而可以为空的类型带有2.0(因为它们需要泛型)。那么,为什么不必要地打破兼容性,或者开始在某些类型的台盼之间引入不一致性呢?您总是可以编写自己的扩展方法来复制已经存在的功能(请参阅EricLipperts关于一个无关主题的文章,其中包括做/不做事情背后的一些推理)。

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


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

    1
    2
    3
    4
    5
    StatusInfo a, b, c;

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

    VS

    1
    2
    3
    StatusInfo a = Initialize();
    StatusInfo b = Validate(a);
    StatusInfo c = Process(b);

    至少对我来说,我在扫描的时候非常强调每行的前几个字符。在确认声明了一些"statusInfo"变量之后,我可以很容易地分辨出第一个示例中发生了什么。在第二个示例中,我看到的第一件事是检索到一堆statusinfo。我必须再扫描一次,看看这些方法会产生什么样的效果。


    我不能将空值传递给台盼函数的out参数,这确实让我恼火。

    不过,在某些情况下,我更喜欢它返回带有两个数据段的新类型。尤其是当它们在大多数情况下都是无关的,或者只有一个零件在一瞬间后才需要进行一次操作时。当我确实需要保存一个函数的结果值时,我非常喜欢有一个out参数,而不是一些我必须处理的随机结果和值类。


    是的,这是有道理的。以这个为例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    String strNum ="-1";
    Int32 outNum;

    if (Int32.TryParse(strNum, out outNum)) {
        // success
    }
    else {
        // fail
    }

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


    为返回值而创建一个类型对我来说似乎没有什么痛苦:—)首先,我必须创建一个返回值的类型,然后在调用方法中,我将返回类型的值分配给需要它的实际变量。

    输出参数与使用类似。