用C#7编写“ Try”方法的最优雅方法是什么?


21

我正在编写一种类型为Queue的实现,该实现的TryDequeue方法使用与各种.NET TryParse方法类似的模式,如果操作成功,我将在其中返回一个布尔值,并使用一个out参数来返回实际出列的值。

public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);

现在,我想尽可能避免使用outparams。C#7为我们提供了变量代用,以使使用它们变得更容易,但是我仍然认为,参数比起有用的工具更多的是必要的弊端。

我想要此方法的行为如下:

  • 如果有要出队的物品,请将其退回。
  • 如果没有要出队的项目(队列为空),请向调用者提供足够的信息以使其正确执行操作。
  • 如果没有剩余的项目,不要只是返回一个空项目。
  • 如果尝试从空队列中出队,请不要抛出异常。

现在,此方法的调用者几乎总是使用如下模式(使用C#7 out变量语法):

if (myMessageQueue.TryDequeue(out Message dequeued))
    MyMessagingClass.SendMessage(dequeued)
else
    Console.WriteLine("No messages!"); // do other stuff

总而言之,这还不是最糟糕的。但是我不禁感到可能有更好的方法可以做到这一点(我完全愿意承认可能没有)。我讨厌调用者如何只需要在有条件的情况下获得一个值就可以有条件地打破它的流程。

还有哪些其他模式可以完成相同的“尝试”行为?

就上下文而言,此方法可能会在VB项目中调用,因此在这两种方法中都可以很好地使用的方法可获得加分。不过,这个事实应该没有太大的意义。


9
定义一个Option<T>结构并返回它。这些bool Try(..., out data)功能令人讨厌。
CodesInChaos

2
我在想类似的...如果一个对OUT参数不利,也许<T>,Option <T>。
乔恩·雷诺

1
@FrustratedWithFormsDesigner很好,我还没有“尝试过”其他事情(欢迎使用双关语),也没有想到太多。我有一个返回ValueTuple的想法,但充其量我认为这没有什么改善。
埃里克·桑德加德

@CodesInChaos我们在同一页面上,这就是为什么我在这里!我喜欢这个主意。如果您有时间,您是否愿意在答案中提供更多细节,以便我接受?
埃里克·桑德加德

Answers:


24

使用Option类型,即具有两个版本的类型的对象,通常称为“ Some”(当存在值时)或“ None”(当没有值时)...或偶尔他们被称为“正义与虚无”。然后,这些类型中的函数可以让您访问该值(如果存在),测试是否存在,最重要的是,一种方法可以让您应用一个函数,如果该值存在,则该函数返回另一个Option(通常在C#中,应该被称为FlatMap,尽管在其他语言中它通常被称为Bind。。。这个名称在C#中很关键,因为拥有此名称和类型的s方法,让我们在LINQ语句中使用Option对象。

其他功能可能包括诸如IfPresent和IfNotPresent之类的方法,以在相关条件下调用动作,以及OrElse(在不存在任何值的情况下替代默认值,否则为no op)。

您的示例如下所示:

myMessageQueue.TryDeque()
    .IfPresent( dequeued => MyMessagingClass.SendMessage(dequeued))
    .IfNotPresent (() =>  Console.WriteLine("No messages!")

这是Option(或也许是)monad模式,它非常有用。有现有的实现(例如 https://github.com/nlkl/Optional/blob/master/README.md),但您自己也不难。

(您可能希望扩展此模式,以便在方法失败时返回错误原因的描述,而不是一无所获……这是完全可以实现的,通常被称为Either monad;顾名思义,您可以使用相同的FlatMap模式,以便在这种情况下也可以轻松使用)


感谢您的建议和您的想法,这绝对是一个不错的选择。我真的很想在这里探索更多的想法。
埃里克·桑德加德

2
这种做法的好处约Option/ Maybe是,它是一个单子(如果实现为一个,当然),这意味着它可以被安全地链接,包裹,解开,并处理而没有需要直接处理不同的情况,这个链接和可以使用LINQ查询表达式来完成处理。
约尔格W¯¯米塔格

3
顺便说一句,.NET中会调用flatMap/ 。bindSelectMany
约尔格W¯¯米塔格

5
我想知道选择一个不同的名称是否是一个更好的主意,因为这样做会破坏Try....NET中的命名约定(并可能会使维护者感到困惑)。
鲍勃

@Bob这很重要。我同意。
埃里克·桑德加德

12

给出的答案很好,我会选择其中之一。认为这个答案只是在一些角落提出了一些替代方案:

  • 制作Message被称为SomeMessage和的子类NoMessageDequeue现在可以返回,NoMessage如果没有消息,并且SomeMessage有消息。如果被呼叫者希望检测出他们所处的情况,则可以通过类型检查轻松地做到这一点。如果他们不这样做,NoMessage那么只要调用它的任何方法就什么都不做,嘿,他们得到了他们所要求的。

  • 与上述相同,但Message隐式转换为bool(或实现operator true / operator false)。现在您可以说if (message),如果消息好,则为真,如果消息为坏,则为假。(这几乎肯定是一个坏主意,但出于完整性考虑,我将其包括在内!)

  • C#7具有元组。返回一个(bool, Message)元组。

  • 生成Message一个struct类型并返回Message?


嘿,谢谢你的回答。对于这个问题,我正试图将自己暴露于不同的想法,就像我试图为自己的特定问题找到解决方案一样。干杯!
埃里克·桑德加德

我的意思是,如果Unity使用了您的“坏主意”,为什么我们不能使用它?
Arturo TorresSánchez18年

@ArturoTorresSánchez:您的问题是“其他人使用了非常糟糕的编程习惯,所以我为什么不能呢?” 这个问题是不连贯的。没有人阻止您使用与其他人相同的不良编程习惯。你去吧。那将不会使其成为C#中的良好实践。
埃里克·利珀特

这是面颊的舌头。我想我确实需要使用“ / s”进行标记。
Arturo TorresSánchez18年

@ArturoTorresSánchez:这是一个问答网站;我认为问题是问题,正在寻求答案
埃里克·利珀特

10

在C#7中,您可以使用模式匹配以更优雅的方式实现相同功能:

if (myMessageQueue.TryDequeue() is Message dequeued) 
{
     MyMessagingClass.SendMessage(dequeued)
} 
else 
{
    Console.WriteLine("No messages!"); // do other stuff
}

在这种情况下,我可能会使用事件,而不是反复轮询队列。


3
但这不是要求TryDequeue返回带有Message实现的标记界面和“无”实现的标记吗?当然,它在调用站点上更为优雅,但是您在实际的代码中只能实现3种实现,如果Message是现有类型,则这本身是不可能的。
Telastyn

1
@Telastyn:如果它只返回null,那么它也可以工作。您也可以使用选项类型。
JacquesB

@JacquesB:但是如果null为有效的可排队项目怎么办?
JBSnorro

5

对于失败(无值)和成功一样普遍的情况,Try ...方法(具有out参数)没有任何问题。

但是,如果您坚持以不同的方式处理问题,则可能要返回一条空消息,或者只是发送该消息(不再是您的问题),或者将if语句推迟到您真正必须知道是否有一条包含内容的消息为止。

请注意,无论如何,有人必须随时进行测试。我认为应该在当前问题所在的时间和地点进行测试。这是在出队时。


你说的对。无论您做什么,都仍然需要测试。坦白说,这时我可能仍然没有参数(事实上,我目前正在公开多个“ TryDequeue”实现,只是为了摆弄每个实现)。我真的只是想讨论可能存在的其他选项。
艾瑞克·桑德加德
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.