以通用方式实现 Continuation - C# 中 Option 类型的用法 - 使代码更清晰





5.00/5 (2投票s)
C# 中的 Continuation - 使代码更清晰。
引言
需要解决的问题:如果你看很多 C# 代码,代码必须从上到下阅读,遵循所有的 If
、Else
、Try
和其他分支才能真正理解发生了什么。此外,很多代码在技术上都很普通,例如检查 null
值。我想知道是否可以编写从左到右阅读的代码,并且原则上是非技术性的。它看起来像一系列有意义的操作。F# 中有一个‘类似’的概念称为管道(pipelining)。在 F# 中,它看起来像这样:
Let squared = Numbers |> List.filter isOdd |> List map square;;
对象 Numbers 被通过后续的函数进行管道化。在 C# 中,我想做类似的事情:
[SomeObject].AndThen(isValid)
. AndThen(Save)
. AndThenIfElse(Do_this_If_ThisIsTrue, Do_this_IfOtherwise)
等等。在这种情况下,SomeObject
被传递给函数 IsValid
,然后传递给 Save
,再传递给 Do_this_If_ThisIsTrue
,该函数可以返回 'false
',在这种情况下,SomeObject
被传递给 Do_this_ifOtherWise
。这种链式调用的好处是我们可以根据需要添加任意数量的 AndThen
、AndThenIfElse
。
多年前,你可以使用委托(delegates)来实现这一点。委托最终不是通用的。现在我们可以使用它们,但我们也想处理错误处理和 null
类似的代码。Tomas Petricek 和 John Skeet 的《Real-World Functional Programming》提供了解决方案。秘诀在于两个主要方面:Option 类型和仅使用一个固定的接口:(func
of Option
of T
, Option
of T
),这意味着你传递一个接收 Option
并返回一个 Option
的函数。这个接口可以根据需要进行链式调用或管道化。
Option
类型是 F# 中一个广为人知的类型。一个 Option
可以是 None
或 Some
。Some
封装了一个实际的业务对象。Continuation 是将函数的结果传递给另一个函数的概念。
如果你将这两者结合起来会怎么样?这里涉及很多理论,但我不会让你烦恼。如果你将一个对象包装在 Option
中,然后使用 Continuation
,你可以像上面 AndThen
代码所示的那样继续链式调用。Option
对象隐藏了其中的实际对象类型。
Option
内部可以包含任何东西。利用 Option
概念,我们可以让强类型语言以通用方式运行。
对于 continuation,你可以使用 Lambda 表达式,但这样的话类型系统将不允许你以通用方式实现它们。
本文旨在实现通用方式的 Continuation。
Option
类型是一个 abstract
类,有两个具体实现:None
和 Some
。None
结束链式调用,Some
封装了 [SomeObject]
。函数必须先获取封装的对象,对其进行一些处理,然后返回 None
或一个包含封装对象的 Some
。Option Some
和 None
类是泛型的!
在这个 Option
类型中,我添加了一些函数,例如上面的 AndThenIfElse
。如果你查看参数,你会看到你传递了以下两个参数:Func
of Option
of T
, Option
of T tryFirst
, Func
of Option
of T
, Option
of T tryNext
,这意味着你传递了两个函数。
我添加了
AndThen
(continuation)。如果前一个函数返回Some
,则继续执行下一个函数。AndThenCheck
。你将一个接收Option
并返回Boolean
的函数作为第一个参数传递。如果这个Boolean
为true
,则执行第二个参数,这是一个 continuation。如果不是true
,则返回 None。AndThenCaseCheck
。你传递一个AndThenCheck
列表。如果某个检查返回true
,则执行伴随的 continuation。如果没有检查返回true
,则返回None
。在附带的示例中,我使用 Lambda 表达式来组合这个列表。Funcs 可以被 Lambda 表达式替换,在这种情况下,我认为这是一个好主意。AndThenIfElse
。你传递两个函数。如果第一个返回None
,则执行第二个 continuation。
另一个值得注意的有趣之处是,上面的 continuation 管道直到调用 Exec()
才会被执行。这样做的原因是,异常仅在 Exec()
调用中处理,并且 continuation 在遇到第一个 None
返回时停止。管道实际上是一个 MultiCastDelgates
的 ArrayList,可以进行序列化。Option/Continuation 概念也可以用于其他场景,例如通过保存执行直到触发。
我添加了一个如何在实际中使用它的具体示例。代码本身并没有做什么,但能让你很好地了解如何使用它。查看下面的代码(ClientController
),你会看到它的真正好处。
someClientData.AndThen(isValid)
.AndThen(ClientBusiness.Save)
.AndThenIfElse(ClientBusiness.CheckOnDustin, ClientBusiness.CheckOnNotDustin)
.AndThenCheck(ClientBusiness.ANameLengthCheck, ClientBusiness.GetLastOneInLocality)
.AndThenCaseCheck(ClientBusiness.SomeCaseListOnProfession())
.Exec();
代码是从左到右阅读的吗?它看起来像一系列链式操作吗?它是否“隐藏”了技术处理,而侧重于业务处理?你认为呢?告诉我。
历史
- 2011年8月23日:初版