Обнуляемые ссылочные типы и «[CS8603] Возможен возврат нулевой ссылки». - PullRequest
0 голосов
/ 10 ноября 2019

Я почти успешно обновил свою библиотеку, чтобы она была «осведомлена о нуле». Однако следующий код доставляет мне неприятности.

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;
        }
    }
}

Компилятор выдает мне предупреждение для return с [CS8603] Possible null reference return, которое я могу понять, видя, как я инициализирую result с null. Однако метод никогда не может вернуть ноль, result всегда будет последним возвращаемым значением из validator.

Является ли pragma warning diable моим единственным вариантом здесь, или я что-то упустил? Я должен добавить, что я не хочу менять тип возвращаемого значения моего метода Either (как опять-таки, он должен быть нулевым).

Редактировать : Переименован класс Eitherна Result (тип возврата), так как это отвлекает от вопроса. Также включен код для класса. Как было сказано в комментариях, этот тип действительно был вдохновлен FP, если быть точным, от Kotlin Arrow, но никогда не предполагалось, что он будет вести себя именно так.

Ответы [ 2 ]

0 голосов
/ 13 ноября 2019
// Warning produced here
return result! as Result<string, TValid>.Valid;

Вы используете оператор as, который может возвращать null, поэтому результирующий тип будет иметь значение NULL. Используйте as, когда вы думаете, что тип может не соответствовать, и вы хотите, чтобы он оценивался до null в тех случаях, когда это не так. Если вы считаете, что оно всегда должно совпадать, используйте обычное приведение.

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

Это сгенерирует, если ваше предположение, что result должен иметь тип Valid, неверно, что вы и хотите. Возможно, вы также захотите добавить проверку null, чтобы убедиться, что вы не возвращаете null (например, если последний validator вернул null.) (Допустимо использовать значение null, поэтомуВ ролях не поймаешь.)

0 голосов
/ 11 ноября 2019

Вопрос неясен даже для функциональных программистов, потому что он не содержит информации о типе Either. Это пользовательский класс, он взят из определенной библиотеки? Что это за API?

C # не имеет типа Either и F #, которые имеют различимые объединения, а Result Type использует другой синтаксис и фактически только люди, которые немного знаюто Haskell может знать, что тип left должен быть значением «error».

В любом случае, функциональный способ объединения Результатов состоит в том, что каждая функция получает вход предыдущей и, если это Ошибка, немедленно возвращает ее. Если нет, примените функцию. Это часто называют Железнодорожно-ориентированное программирование .

Если у вас есть список / перечисление / массив функций (в данном случае валидаторы), вы можете использовать fold в F # или в Агрегате LINQ вC # для вызова каждого валидатора и передачи ему предыдущего результата. Не зная, как выглядит Either, или вы можете создать для него значения, я могу предложить только пример псевдокода:

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;
}
...