Как добавить валидацию на Null на уровне конструктора?

abstract class Base
{
    public Base(int i)
    {
        
    }
}

class Foo : Base
{
    public Foo(string s) : base(s.Length)
    {
        
    }
    
}

С учётом того, что Base класс может быть в другой библиотеке, к изменению которой доступа нет. Пробовал null coalescing оператор, но ругается, что левая часть выражения должна быть ссылочного или nullable типа:

public Foo(string s) : base(s.Length ?? 0)

Буду рад любым подсказкам.


Ответы (2 шт):

Автор решения: CrazyElf

Ну можно тернарный оператор использовать:

base(s == null ? 0 : s.Length)

Или так тоже можно

base(s?.Length ?? 0)

Хотя не очень понятно, какое вы поведение всё же хотите в случае null, но предположим, что такое.

Валидация обычно предполагает, что если аргумент неподходящий, то нужно бросать исключение или ещё как-то сигнализировать об ошибке, а тут вы пытаетесь приспособиться к ситуации. А может не надо этого делать?

→ Ссылка
Автор решения: VladD

Насколько я понимаю, вам нужно не подавить ошибку, а бросить исключение. Проблема в том, что вы не можете это сделать «внутри» тела конструктора, т. к. до начала работы тела конструктора вам нужно вызвать базовый конструктор, а проверка должна быть до вызова базового конструктора, то есть на этапе вычисления аргументов для базового конструктора.

Самый простой код, который это делает, видимо, такой:

public Foo(string s) : base((s ?? throw new ArgumentNullException(nameof(s))).Length)
{
}

Это выглядит нечитаемо (особенно если таких аргументов у вас много), поэтому, возможно, лучше вынести проверку в отдельную статическую функцию:

static string Checked(string s) => s ?? throw new ArgumentNullException("s");

public Foo(string s) : base(Checked(s).Length)
{
}

Ну или если хочется в старом стиле, то

static string Checked(string s)
{
    if (s == null)
        throw new ArgumentNullException("s");
    return s;
}

Поскольку метод довольно общий, можно вынести его в отдельный helper и применять:

static class ValidationHelper
{
    public static T EnsureNotNull(T? t, string name) where T : class =>
        t ?? throw new ArgumentNullException(name);
}

и пользоваться как-то так:

using static ValidationHelper;

...

public Foo(string s) : base(EnsureNotNull(s, nameof(s)).Length)
{
}

Когда в будущем в C# реализуют-таки [CallerArgumentExpression], можно будет писать изящнее:

static class ValidationHelper
{
    public static T EnsureNotNull(
        T? t,
        [CallerArgumentExpression(nameof(t))] string name = null)
            where T : class =>
        t ?? throw new ArgumentNullException(name);
}

...

using static ValidationHelper;

...

public Foo(string s) : base(EnsureNotNull(s).Length)
{
}
→ Ссылка