Rx 是一种函数式范例,因此使用函数式方法来解决这个问题非常有用。
答案是引入另一个可以处理错误的 monad,例如Nullable<T>
可以处理具有空值的整数,但在这种情况下,类可以表示值或异常。
public class Exceptional
{
public static Exceptional<T> From<T>(T value) => new Exceptional<T>(value);
public static Exceptional<T> From<T>(Exception ex) => new Exceptional<T>(ex);
public static Exceptional<T> From<T>(Func<T> factory) => new Exceptional<T>(factory);
}
public class Exceptional<T>
{
public bool HasException { get; private set; }
public Exception Exception { get; private set; }
public T Value { get; private set; }
public Exceptional(T value)
{
this.HasException = false;
this.Value = value;
}
public Exceptional(Exception exception)
{
this.HasException = true;
this.Exception = exception;
}
public Exceptional(Func<T> factory)
{
try
{
this.Value = factory();
this.HasException = false;
}
catch (Exception ex)
{
this.Exception = ex;
this.HasException = true;
}
}
public override string ToString() =>
this.HasException
? this.Exception.GetType().Name
: (this.Value != null ? this.Value.ToString() : "null");
}
public static class ExceptionalExtensions
{
public static Exceptional<T> ToExceptional<T>(this T value) => Exceptional.From(value);
public static Exceptional<T> ToExceptional<T>(this Func<T> factory) => Exceptional.From(factory);
public static Exceptional<U> Select<T, U>(this Exceptional<T> value, Func<T, U> m) =>
value.SelectMany(t => Exceptional.From(() => m(t)));
public static Exceptional<U> SelectMany<T, U>(this Exceptional<T> value, Func<T, Exceptional<U>> k) =>
value.HasException ? Exceptional.From<U>(value.Exception) : k(value.Value);
public static Exceptional<V> SelectMany<T, U, V>(this Exceptional<T> value, Func<T, Exceptional<U>> k, Func<T, U, V> m) =>
value.SelectMany(t => k(t).SelectMany(u => Exceptional.From(() => m(t, u))));
}
因此,我们首先创建一个抛出异常的 Rx 查询。
IObservable<int> query =
Observable
.Range(0, 10)
.Select(x => 5 - x)
.Select(x => 100 / x)
.Select(x => x + 5);
如果我运行可观察的结果,我会得到:
让我们用以下方法来改变它:Exceptional
并看看它如何允许我们在发生错误时继续处理。
IObservable<Exceptional<int>> query =
Observable
.Range(0, 10)
.Select(x => x.ToExceptional())
.Select(x => x.Select(y => 5 - y))
.Select(x => x.Select(y => 100 / y))
.Select(x => x.Select(y => y + 5));
现在当我运行它时我得到这个:
现在我可以测试每个结果,看看是否HasException
is true
并记录每个异常,同时可观察的继续。
最后,通过引入另一种扩展方法,可以轻松清理查询,使其看起来与原始查询几乎相同。
public static IObservable<Exceptional<U>> Select<T, U>(this IObservable<Exceptional<T>> source, Func<T, U> m) =>
source.Select(x => x.SelectMany(y => Exceptional.From(() => m(y))));
这将可观察到的情况和异常情况结合到一个单一的Select
操作员。
现在查询可以如下所示:
IObservable<Exceptional<int>> query =
Observable
.Range(0, 10)
.Select(x => x.ToExceptional())
.Select(x => 5 - x)
.Select(x => 100 / x)
.Select(x => x + 5);
我之前得到了同样的结果。
最后,我可以通过添加另外两个扩展方法来使用查询语法来实现这一切:
public static IObservable<Exceptional<U>> SelectMany<T, U>(this IObservable<T> source, Func<T, Exceptional<U>> k) =>
source.Select(t => k(t));
public static IObservable<Exceptional<V>> SelectMany<T, U, V>(this IObservable<T> source, Func<T, Exceptional<U>> k, Func<T, U, V> m) =>
source.SelectMany(t => k(t).SelectMany(u => Exceptional.From(() => m(t, u))));
这允许:
IObservable<Exceptional<int>> query =
from n in Observable.Range(0, 10)
from x in n.ToExceptional()
let a = 5 - x
let b = 100 / a
select b + 5;
我再次得到与以前相同的结果。