我应该使用哪种返回风格?

10

这与C#中使用的惯例有关。

我有一个具有两个参数(X和Y坐标)的方法。 这些坐标表示“瓷砖”可能驻留的位置。 如果瓷砖位于这些坐标处,则该方法返回其编号。 如果没有瓷砖位于这些坐标处,我想知道该方法应该如何行动。

我看到三个选项:

  1. 使用异常。 每次Method未找到瓷砖时,我可以引发异常。 但是,由于这种情况并不罕见,因此此选项是最差的。
  2. 以传统的C ++方式处理,并在没有瓷砖的情况下返回-1。
  3. 将瓷砖编号作为引用参数,并更改方法的返回类型为布尔型,以显示是否有瓷砖。 但是,这对我来说似乎有点复杂。

那么,我该怎么办?


1
看起来,我开始了一场圣战 :) - undsoft
4
支持圣战!+1 ;) - Adrien
大家,感谢你们的回答。 我之前不知道可空类型,以后会记住的。 但为了简化一切,现在我会使用“返回-1”的选项。 - undsoft
12个回答

23
您可以返回null,并在调用代码中检查这一点。当然,您必须使用可空类型:
int? i = YourMethodHere(x, y);

1
如果不返回对象,则返回 null 是正确的方式。 - Oliver Friedrich
1
“必须使用”?我完全不同意。 - Colin Burnett
"你必须使用" - 这意味着函数返回类型应该是可空的。 "强制方法自动装箱" - 不,可空类型是值类型。 可空值的问题可能在于同时在VB.NET和C#中操作 - 请参考http://www.panopticoncentral.net/archive/2004/06/04/1180.aspx。 - Roma
可空类型是 System.Nullable(T) 结构的实例。 - Roma
@Blaenk - 这正是我想表达的。 - Jay Riggs
显示剩余3条评论

20

返回-1。

这不仅是C++的惯例,.NET框架也常用 - 例如,对于表示列表的控件,像是String.IndexOf方法或SelectedIndex属性等。

编辑

只是为了进一步解释,在您的问题中的三个选项(异常、返回-1、输出参数)中,返回-1是最好的选择。 异常是用于异常情况,Microsoft编码指南建议尽可能避免使用输出参数。

在我看来,返回-1(前提是它总是无效值)、返回可空整数或Tile对象都是可以接受的解决方案,您应该选择与应用程序其余部分最一致的方案。 我无法想象任何开发人员会对以下任何一个有丝毫困难:

int tileNumber = GetTile(x,y);
if  (tileNumber != -1)
{
   ... use tileNumber ...
}


int? result = GetTile(x,y);
if (result.HasValue)
{
    int tileNumber = result.Value; 
   ... use tileNumber ...
}


Tile tile = GetTile(x,y);
if (tile != null)
{
   ... use tile ...
}

我不太确定我理解Peter Ruderman关于使用int比返回可空类型“更高效”的评论。我认为任何差异都应该是微不足道的。


3
这些方法之所以普遍,是因为在它们被创建的时候 nullables 还不存在,或者因为一直沿用这种普遍性(即旧习惯难以改变)。我认为,nullables 是一个更明智的选择。 - configurator
1
这也很常见,因为它比返回可空类型更有效率。 - Peter Ruderman
5
"Nullables是一个更为明智的选择。这取决于上下文。最重要的是要与应用程序中使用的约定保持一致。" - Joe
1
看来我对它更高效的评价过早了。罗曼是正确的。System.Nullable<T>是一个结构体,因此是值类型。我的错。 - Peter Ruderman
我更喜欢使用if(tileNumber >= 0)而不是if(tileNumber != -1)。对于这些类型的函数来说,任何小于0的值都是无效的结果,所以这是一个好习惯。例如,对于Array.BinarySearch,如果未找到该值,则返回-(insertionIndex + 1)。 - P Daddy

17

异常是为了处理特殊情况而设计的,因此在已知且预期的错误情况下使用异常是不好的。现在,您更有可能在各处使用try-catch来专门处理这种错误情况,因为您预期这种错误情况会发生。

如果您的唯一错误条件(如-1)很容易与实际值混淆,则将返回值作为参数是可接受的。如果您可以有一个负数图块编号,那么这是更好的方法。

可空int是引用参数的一个可能替代方案,但通过这种方式创建对象,因此如果“错误”是常规情况,则可能比引用参数方式更加繁琐。正如Roman在其他评论中指出的,您将面临C#与VB问题,因为对于VB来说,可空类型的引入时间过晚,无法提供像C#一样的简洁语法糖。

如果您的图块只能是非负数,则返回-1是指示错误的可接受且传统的方式。从性能和内存的角度来看,它也将是最便宜的选择。


考虑到自我文档化,还有另一种方式可供考虑。使用-1和异常是惯例:您必须编写文档以确保开发人员知道它们的存在。使用int?返回或引用参数将更好地自我描述,并且不需要为开发人员了解如何处理错误情况而要求文档。当然,您应该始终编写文档,就像每天清洁牙齿一样。


1
我不同意你的第一句话。异常是针对那些方法无法实现其承诺的情况而设计的。这种情况应该很少发生,但这并不是最重要的。当然,我们也需要考虑性能方面的问题。 - Ari Roth
2
Pyran,你在挑刺。undsoft 知道未找到的瓷砖是常见的,它应该以优雅的方式处理缺失的瓷砖。例如,如果坐标超出了边界,则这将是异常情况。然而,如果这是一个靠近用户输入的方法,并且预计会经常出现越界错误,那么再次抛出异常就是错误的做法。 - Colin Burnett
可空类型是 System.Nullable(T) 结构的实例。- 请参阅 MSDN。 - Roma
可空类型是结构体,这意味着您不需要创建对象,因此不会引入额外的开销(也不会增加更多的工作)。关于C#和VB.NET问题,它们并不是问题,只是需要了解一些事情。如果可空类型可以在整个项目中使用,我会投票支持它们。否则,我会投票支持“-1”。 - Roma
Roman,创建一个结构体仍然会产生创建的开销(你必须在某个地方存储该值)。我还没有深入研究过Nullable<T>,但至少它必须包含值的副本以及表示是否有值的布尔值。这并不是什么大问题,但也不是零开销。帖子中提到了瓦片,让我想到了游戏/游戏开发,这是你(据我所知)非常努力追求效率的领域。不必要的数据重复似乎是你想要消除的事情之一。 - Colin Burnett

6

使用可空的返回值。

int? GetTile(int x, int y) {
   if (...)
      return SomeValue;
   else
      return null;
}

这是最清晰的解决方案。

3
如果您的方法可以访问底层瓷砖对象,另一种可能性是返回瓷砖对象本身,如果不存在这样的瓷砖,则返回 null。

无论如何,这就是我会做的,如果需要的话,你可能仍然可以访问瓦片编号。 - Olivier Tremblay

2
最好的选择是返回布尔值或返回null。
例如:
bool TryGetTile(int x, int y, out int tile);

或者,
int? GetTile(int x, int y);

有几个原因可以选择“TryGetValue”模式。首先,它返回一个布尔值,因此客户端代码非常直观,例如: if (TryGetValue(out someVal)) { /* some code */ }。相比之下,客户端代码需要硬编码的哨兵值比较(-1、0、null、捕获特定的一组异常等),这些设计很快就会出现“魔数”,而将紧密耦合的内容分解出来则变成了一项繁琐的工作。
当预期哨兵值、null或异常时,绝对必要检查文档中使用的机制。如果文档不存在或无法访问,则需要根据其他证据进行推断。如果您做出错误的选择,那么您只是在为自己设置空引用异常或其他错误缺陷铺平道路。而“TryGetValue()”模式仅从其名称和方法签名上就非常接近于自我记录。

如果你必须返回false(“没有瓷砖”),tile参数将具有什么值?当然,它可以是任何值,但对于int类型来说,这很奇怪。如果它是一个对象,将其设置为null并返回false就可以了。如果它是一个int,并且有人忘记检查函数的返回值,他会使用错误的值(比如“0”或“-1”)。这不是一种好的编码方式。 - Roma
@Roman,我认为你的推理是错误的。如果获取失败,TryGetTile()可以对输出值进行任何操作,最有可能的情况是它不会修改输出参数。说这比其他方法更危险相当荒谬。在这里,绝对没有比如果GetTile返回null并且在对返回值执行操作之前忘记检查null更危险的情况。事实上,TryGetTile本身的命名使得它应该如何使用变得非常清晰,并且使得在代码中发现这样的错误变得容易得多。 - Wedge

2
我会选择方案2。你说得对,在如此常见的情况下抛出异常可能会影响性能,而使用一个out参数并返回true或false虽然有用但不易阅读。
另外,想想string.IndexOf()方法。如果没有找到,它返回-1。我会遵循这个例子。

2
你可以返回-1,因为这是C#中常见的做法。然而,更好的方法是实际返回被点击的瓷砖,并在没有点击瓷砖的情况下返回对单例NullTile实例的引用。这样做的好处是,你给每个返回的值赋予了具体的含义,而不仅仅是一个数字,它的内在意义超出了其数值之外。类型“NullTile”非常明确地说明了其含义,对于代码的其他读者来说很容易理解。

1

关于你提出的问题,我有自己的看法,但已经在上面表述过了,并且我已经投票了。

至于你没有问到的问题,或者至少作为所有上面答案的扩展:我会确保在应用程序中将类似情况的解决方案保持一致。换句话说,无论你最终选择什么答案,在应用程序中都要保持一致。


0

你是否可能已经创建(或可以创建)一个在指定坐标引用的Tile对象?如果是这样,你可以返回该瓷砖的引用,如果给定坐标上没有瓷砖,则返回null

public Tile GetTile(int x, int y) {
    if (!TileExists(x, y)) 
        return null;
    // ... tile lookup here...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接