可为空的引用类型和“ [CS8603]可能返回的空引用”。

时间:2019-11-10 14:22:25

标签: c# c#-8.0 nullable-reference-types

我几乎成功地将我的一个库更新为“可空的”。但是下面的代码给我带来麻烦。

public static Result<string, TValid> ResultMustBe<TValid>
(
    this TValid self,
    params Func<TValid, Result<string, TValid>>[] validators
)
{
    var caller = new StackFrame(1)?.GetMethod()?.DeclaringType?.FullName ?? "?";

    if (!validators.Any())
    {
        throw new ArgumentException($"No validators provided for EitherMustBe in {caller}");
    }

    Result<string, TValid>? result = null;

    foreach (var validator in validators)
    {
        try
        {
            result = validator(self);

            if (result is Result<string, TValid>.Invalid left)
            {
                return Result<string, TValid>.MakeInvalid(left.Error.Replace("<<caller>>", caller));
            }
        }
        catch (Exception ex)
        {
            throw new ValidationException($"Uncaught exception occured while validating {caller}", ex);
        }
    }
    // Warning produced here
    return result! as Result<string, TValid>.Valid;
}
public abstract class Result<TInvalid, TValid>
{
    public static Valid MakeValid(TValid data)
    {
        return new Valid(data);
    }

    public static Invalid MakeInvalid(TInvalid error)
    {
        return new Invalid(error);
    }

    public abstract bool IsValid { get; }

    public class Invalid : Result<TInvalid, TValid>
    {
        public TInvalid Error { get; }
        public override bool IsValid => false;

        public Invalid(TInvalid error)
        {
            Error = error;
        }
    }

    public class Valid : Result<TInvalid, TValid>
    {
        public TValid Data { get; }
        public override bool IsValid => true;

        public Valid(TValid data)
        {
            Data = data;
        }
    }
}

编译器向我发出[CS8603] Possible null reference return返回警告,我可以用result初始化null来理解。但是,该方法永远不会实际返回null,result始终是validator的最新返回值。

pragma warning diable是我唯一的选择,还是我错过了什么?我应该补充一点,就是我不想更改方法的返回类型(同样,它应该是null安全的)。

编辑:由于分散了对问题的注意力,将类Either重命名为Result(返回类型)。还包括该类的代码。正如评论中所问的那样,该类型确实是受FP启发的,确切地说是来自Kotlin Arrow的FP,但从未打算完全像它那样表现。

2 个答案:

答案 0 :(得分:0)

// Warning produced here
return result! as Result<string, TValid>.Valid;

您正在使用as运算符,该运算符可能返回null,因此结果类型将可以为空。如果您认为类型可能不匹配且希望在不匹配的情况下求值为as,请使用null。如果您认为它应该始终匹配,请使用常规强制转换。

return (Result<string, TValid>.Valid)result!;

如果您认为result必须为Valid类型的假设是错误的,这就是您想要的。您可能还想添加一个null检查,以确保您不会以某种方式返回null(就像最后一个validator返回了null一样。)(强制转换null的值,因此强制类型转换不会捕获该值。)

答案 1 :(得分:-1)

这个问题甚至对于函数式程序员来说也不清楚,因为它不包含有关Either类型的信息。它是一个自定义类,它来自特定的库吗?什么是API?

C#没有Either类型,而F#没有区别联合,而Result Type使用不同的语法,实际上,只有对Haskell有所了解的人才知道 left < / em>类型应该是“错误”值。

无论如何,链接结果的功能方式是使每个函数都接收前一个输入,如果是错误,则立即返回。如果不是,则应用该功能。这通常称为Railway oriented programming

如果您具有函数的列表/可枚举/数组(在本例中为验证器),则可以在F#中使用fold或在C#中使用LINQ的Aggregate来调用每个验证器并将先前的结果传递给它。不知道是什么样子,或者您可以为其构造值,我只能提出一个伪代码示例:

Either<string,TValue> ValidateEitherMustBe<TValid>
    (
        this TValid self,
        params Func<TValid, Either<string, TValid>>[] validators
    )
{
    var seed=Either<string, TValid>.MakeValid(self);
    var result=validators.Aggregate((previous,validator) => 
                       previous switch { Either<string, TValid>.Invalid _   => previous,
                                         Either<string, TValid>.Valid value => valid(value),
    });    
    return result;
}