所以 - 不知道是否有人感兴趣 - 我想出了一个非常 初步 在Mike Hadlow 的带领下实施。其中一些感觉不太正确,但这是一个开始。 (话虽如此,我不会使用它 - 你可能会损失一百万美元甚至杀死某人 - 只是我的警告!)
可以编写的代码类型的一个非常简单的示例是
var exp = from a in 12.Div(2)
from b in a.Div(2)
select a + b;
Assert.AreEqual(9, exp.Value());
var exp = from a in 12.Div(0)
from b in a.Div(2)
select b;
Assert.IsTrue(exp.IsException());
Div 方法实现如下:
public static IExceptional<int> Div(this int numerator, int denominator)
{
return denominator == 0
? new DivideByZeroException().ToExceptional<int, DivideByZeroException>()
: (numerator / denominator).ToExceptional();
}
或
public static IExceptional<int> Div_Throw(this int numerator, int denominator)
{
try
{
return (numerator / denominator).ToExceptional();
}
catch (DivideByZeroException e)
{
return e.ToExceptional<int, DivideByZeroException>();
}
}
(我马上就可以看到对 api 的潜在改进,但不确定如何实现它。我认为这个
new DivideByZeroException().ToExceptional<int, DivideByZeroException>()
如果有就更好了
new DivideByZeroException().ToExceptional<int>()
稍后您会看到我的实现,希望有人能够为上述内容重新设计它。)
一元位在这里完成(主要)
public static class Exceptional
{
public static IExceptional<TValue> ToExceptional<TValue>(this TValue result)
{
return new Value<TValue>(result);
}
public static IExceptional<TValue> ToExceptional<TValue,TException>(this TException exception) where TException : System.Exception
{
return new Exception<TValue, TException>(exception);
}
public static IExceptional<TResultOut> Bind<TResultIn, TResultOut>(this IExceptional<TResultIn> first, Func<TResultIn, IExceptional<TResultOut>> func)
{
return first.IsException() ?
((IInternalException)first).Copy<TResultOut>() :
func(first.Value());
}
public static IExceptional<TResultOut> SelectMany<TResultIn, TResultBind, TResultOut>(this IExceptional<TResultIn> first, Func<TResultIn, IExceptional<TResultBind>> func, Func<TResultIn, TResultBind, TResultOut> select)
{
return first.Bind(aval => func(aval)
.Bind(bval => select(aval, bval)
.ToExceptional()));
}
}
主界面指定为
public interface IExceptional<TValue>
{
bool IsException();
TValue Value();
}
我有一个内部接口用于处理已抛出的异常(稍后会详细介绍)
>
internal interface IInternalException
{
IExceptional<TValue> Copy<TValue>();
}
具体实现如下:
public class Value<TValue> : IExceptional<TValue>
{
TValue _value = default(TValue);
public Value(TValue value)
{
_value = value;
}
bool IExceptional<TValue>.IsException()
{
return false;
}
TValue IExceptional<TValue>.Value()
{
return _value;
}
}
public class Exception<TValue, TException> : IInternalException, IExceptional<TValue> where TException : System.Exception
{
TException _exception = default(TException);
public Exception(TException exception)
{
_exception = exception;
}
bool IExceptional<TValue>.IsException()
{
return true;
}
IExceptional<TOutValue> IInternalException.Copy<TOutValue>()
{
return _exception.ToExceptional<TOutValue,TException>();
}
TException GetException()
{
return _exception;
}
TValue IExceptional<TValue>.Value()
{
return default(TValue);
}
}
解释一下……对我来说,最棘手的一点是发生异常时的 Bind 操作。如果您正在处理操作管道并且在流程的早期抛出异常,则需要将该异常延续到管道中,以便在表达式完成时返回的 IExceptional 包含之前发生的异常。这就是 IInternalException 的原因。它使我能够创建相同或(可能不同)类型的新 IExceptional(例如 IExceptional --> IExceptional),但无需知道内部异常的类型即可将异常复制到新的 IExceptional。
毫无疑问,可以进行大量改进。例如,我可以看到您可能希望跟踪 IExceptional 中的错误堆栈。可能有多余的代码或更好的方法来达到目的……但是……这对我来说是一种学习。
如有任何想法/建议,我们将不胜感激。