提问者:小点点

用C#模式匹配避免箭头模式


我已经开始使用C#7的基于类型的模式匹配。 只管理单个基于模式的结果的方法看起来非常干净,并且易于推理。

然而,一旦依赖于第一个基于模式的结果的第二个基于模式的结果创建了箭头反模式,并且只会随着相互依赖的N个结果变得更糟。

下面是一个演示箭头模式的简化示例:

public Result<bool> ValidateSomething(string strId)
{
    var id = Guid.Parse(strId);
    Result<Something> fetchSomethingResult = new SomethingDao.FetchSomething(id);
    switch (fetchSomethingResult)
    {
        case ValueResult<Something> successfulSomethingResult:
            Result<Related> fetchRelatedFieldsResult = new RelatedDao.FetchRelatedFields(successfulSomethingResult.Value.RelatedId);

            switch (fetchRelatedFieldsResult)
            {
                case ValueResult<Related> successfulFieldValueResult:
                    var isValid = successfulSomethingResult.Value.ComparableVal <= successfulFieldValueResult.Value.RelatedComparableVal;
                    return new ValueResult<bool>(isValid);
                case ValueNotFoundResult<Related> _:
                    return new ValueNotFoundResult<bool>();
                case ErrorResult<Related> errorResult:
                    return new ErrorResult<bool>(errorResult.ResultException);
                default:
                    throw new NotImplementedException("Unexpected Result Type Received.");
            }
        case ValueNotFoundResult<Something> notFoundResult:
            return new ValueNotFoundResult<bool>();
        case ErrorResult<Something> errorResult:
            return new ErrorResult<bool>(errorResult.ResultException);
        default:
            throw new NotImplementedException("Unexpected Result Type Received.");
    }
}

以下是result类的定义,供参考:

public abstract class Result<T>
{

}

public class ValueResult<T> : Result<T>
{
    public ValueResult()
    {
    }

    public ValueResult(T inValue)
    {
        Value = inValue;
    }

    public T Value { get; set; }
}

public class ValueNotFoundResult<T> : Result<T>
{
    public ValueNotFoundResult()
    {
    }
}

public class ErrorResult<T> : Result<T>
{
    public Exception ResultException { get; set; }

    public ErrorResult()
    {
    }

    public ErrorResult(Exception resultException)
    {
        ResultException = resultException;
    }
}

有哪些选项可以更好地处理这种类型的代码? 你对前面的例子有什么建议? 如何用基于模式的结果避免箭头反模式?


共1个答案

匿名用户

由于您实际上只是在结果成功的情况下才对结果执行任何操作,因此将开关移到扩展方法似乎相当简单。 您可能需要两个扩展,具体取决于函数返回的是新结果还是只返回T,类似于:

    public static Result<TOut> SelectValue<TIn, TOut>(this Result<TIn> self, Func<TIn, Result<TOut>> select)
    {
        switch (self)
        {
            case ErrorResult<TIn> errorResult: return new ErrorResult<TOut>(errorResult.ResultException);
            case ValueNotFoundResult<TIn> valueNotFoundResult: return new ValueNotFoundResult<TOut>();
            case ValueResult<TIn> valueResult: return select(valueResult.Value);
            default: throw new ArgumentOutOfRangeException(nameof(self));
        }
    }

    public static Result<TOut> SelectValue<TIn, TOut>(this Result<TIn> self, Func<TIn, TOut> select)
    {
        switch (self)
        {
            case ErrorResult<TIn> errorResult: return new ErrorResult<TOut>(errorResult.ResultException);
            case ValueNotFoundResult<TIn> valueNotFoundResult: return new ValueNotFoundResult<TOut>();
            case ValueResult<TIn> valueResult: return new ValueResult<TOut>(select(valueResult.Value));
            default: throw new ArgumentOutOfRangeException(nameof(self));
        }
    }

然后,您可以处理成功案例,如下所示:

        var result = fetchSomethingResult
            .SelectValue(something => new RelatedDao.FetchRelatedFields(something.RelatedId))
            .SelectValue(related => related.CompareVal >= 5);

如果您需要同时使用来自“something”和“related”的值,那么不幸的是,您将需要嵌套调用。

        var result = fetchSomethingResult
            .SelectValue(something => new RelatedDao.FetchRelatedFields(something.RelatedId).SelectValue(related => related.CompareVal >= 5));