此处的用例是,我向 Entity Framework DbContext
添加了一个方法,该方法在保存之前执行一些额外的工作,然后根据结果返回 Either
。一个非常简化的版本看起来像这样......
static async Task<Either<string, Unit>> SaveChangesAsyncEither(string userId) {
// In reality, this would do some auditing, check for concurrency issues, and
// attempt to save the changes. It would return unit if all went OK,
// or an error message if not. Simple check for demonstrating the issue...
if (userId == "jim") {
return "We don't like Jim";
}
return unit;
}
这个方法在我的解决方案中的很多地方都使用了,而且效果很好。
在一个最小的 API 项目中,我有一种如下所示的方法(再次高度简化)...
static async Task<Either<DeviceApiResponseStates, DeviceApiResponseStates>> CreateDevice(string userId) {
// In reality we would have more parameters, create a device, etc before
// calling SaveChangesAsyncEither
return (await SaveChangesAsyncEither(userId))
.Match(() => Right<DeviceApiResponseStates, DeviceApiResponseStates>(DeviceApiResponseStates.OK),
_ => Left(DeviceApiResponseStates.Nah));
}
...其中 DeviceApiResponseStates
是一个 enum
。为简单起见,您可以想象它看起来像这样......
enum DeviceApiResponseStates {
OK,
Nah
}
这三个代码块是我的问题的完整示例。
我希望如果我调用 CreateDevice("jim")
,那么我会得到一个值为 Nah
的 Left
,如果我用任何其他字符串值调用它,我会得到一个值为 OK 的 Right
。
但是,当我尝试这个时...
Console.WriteLine((await CreateDevice(""))
.Match(_ => "Yeah", ex => $"Exception: {ex}"));
...无论字符串值如何,我都会得到“Nah”。
谁能解释一下我做错了什么?
最佳答案
TL;DR:三键编辑:
static async Task<Either<DeviceApiResponseStates, DeviceApiResponseStates>> CreateDevice(string userId)
{
// In reality we would have more parameters, create a device, etc before
// calling SaveChangesAsyncEither
return (await SaveChangesAsyncEither(userId))
.Match(_ => Right<DeviceApiResponseStates, DeviceApiResponseStates>(DeviceApiResponseStates.OK),
_ => Left(DeviceApiResponseStates.Nah));
}
请注意,我已将第一个 Match
参数中的输入参数从 ()
更改为 _
。
...但是为什么?!
确实,这花了我一些时间才弄清楚。
预先说明以下内容可能很重要,因为我不知道您是否熟悉其他函数式语言 - 特别是 F#,还有 Haskell:
在 C# 中,lambda 表达式 () => x
的类型为 Func<T>
,而不是 Func<Unit, T>
。 (F# 更优雅,因为 fun () -> x
确实 指示函数 unit -> 'a
,因为在 F# 中, ()
的类型为 unit
。在 C# 中则不然,其中 lambda 表达式中的输入“无数据”具有特殊的含义语法 () =>
,并且输出具有特殊关键字 void
。因此,为了说明这一点,C# lambda 表达式 x => {}
具有类型 Action<T>
,而在 F# fun x -> ()
中将具有类型 'a -> unit
。
所有这些意味着,当您编写 .Match(() => ...
时,C# 编译器会将该 lambda 表达式解释为 Func<Either<DeviceApiResponseStates, DeviceApiResponseStates>>
,即一个不接受输入的函数。 C# 重载解析器会查找采用 Func<T>
(不是 Func<Unit, T>)
)作为输入的方法或扩展方法,并找到以下方法:
public static B Match<A, B>(this IEnumerable<A> list, Func<B> Empty, Func<Seq<A>, B> More)
{
return Prelude.toSeq(list).Match(Empty, More);
}
在 LanguageExt 的 ListExtensions
中。请注意 Func<B> Empty
参数。
这是 IEnumerable<T>
而不是 Either<L, R>
上的扩展方法。为什么会编译?
嗯,它可以编译,因为 Either<L, R>
实现了 IEnumerable<T>
:
public readonly struct Either<L, R> : IEnumerable<EitherData<L, R>>, IEnumerable, ...
通过使用 _
而不是 ()
,您可以告诉编译器您希望某个值作为 lambda 表达式的输入,而不是不输入。碰巧您期望的输入是 Unit
值。
现在排除了 IEnumerable<T>
扩展方法,而是使编译器能够在 Match
上找到正确的 Either<L, R>
方法。
关于c# - 为什么无论发生什么情况,我的 Either-return 方法总是返回 Left?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74591448/