Pull to refresh

Comments 22

set => _firstName = value ?? throw new ArgumentNullException(nameof(value));
Лично мне больше нравилось, когда по обеим частям ?? или в тернарном операторе были совместимые типы. А теперь можно писать справа что-то без типа, чтобы просто было удобно. Это спорно. C# и так хорош синтаксисом, особой надобности в спорных фичах нет.

Я думаю они совместимы до сих пор, т.к. throw в виде выражения возвращает любой generic тип, который выводится компилятором из второй ветки оператора ?? в виду того что throw не возвращает в обычном смысле.


В F#, где всё является выражением есть похожие функции:


failwith: string -> 'a
raise: exn -> 'a

принимаем строку с описанием ошибки или объект Exception и валим выполнение программы. компилятору подсовываем любой тип какой он хочет, т.к. это уже неважно, функция нормально не вернёт.

Большинство вещей выглядят более-менее интуитивно понятными.
А вот когда выпадет исключение в linq, я честно не уверен:
                       .Select(c => c.Status == Status.None
                       ? throw new InvalidDataException($"Customer {c.Id} has no status and should not be an award recipient")
                       : c)

Если ToList в конце — то видимо на его материализации.
А если foreach — может упасть на вычислении конкретного элемента?
Не понял вопроса, на всякий случай уточню: а вы в курсе, что
в ToList внутри foreach?
            public List<TResult> ToList()
            {
                var list = new List<TResult>();

                foreach (TSource item in _source)
                {
                    list.Add(_selector(item));
                }

                return list;
            }

Был не в курсе, но и не важно это.

Меня смущает другое — когда я делаю linq операцию, она обычно отложена. Выброс исключения видимо тоже откладывается до перечисления элемента?

А что будет со всеми траснляторами в sql? Они справятся с трансляцией исключения? Смысла в этом мало =_=
А что будет со всеми траснляторами в sql? Они справятся с трансляцией исключения?

Фреймворки вида LINQ to SQL, в отличие от LINQ методов для коллекций, используют не делегаты, а деревья выражений (expression trees), по которым строят запрос к базе. В деревьях выражений нельзя использовать throw-выражения.

Такой код не скомпилируется:
void A(Expression<Func<int, bool>> expr) {}
A(x => throw new Exception()); // [CS8188] An expression tree may not contain a throw-expression.

Также в expression'ах нет поддержки других конструкций C# 7: кортежей (tuple literals), объявлений переменных при вызове метода с out-параметром (out var)
Этой информации мне как раз не хватило, стало понятнее, спасибо.
И pattern matching там тоже нет — не скомпилируется даже x is null.
Исключения в свойствах — зло.
Остальное — приятный сахар.
Исключения в свойствах — зло.
Почему это? ExceptionValidationRule в WPF специально для этого и существует. Чем getter/setter cвойств принципиально отличаются от методов, в которых, я надеюсь, исключения не зло?
Почему это? ExceptionValidationRule в WPF специально для этого и существует.

ExceptionValidationRule отлавливает исключения, которые не обязательно сгенерированы внутри свойства. Они могут появляться и до попытки присвоения значения. При той же конвертации.
Да и вообще, WPF и его Data Binding много чего странного привносит в архитектуру решения.

Чем getter/setter cвойств принципиально отличаются от методов, в которых, я надеюсь, исключения не зло?

Тут уже личное мнение. Технически — почти ничем не отличаются. Коцептуально как раз тем, что свойства не должны содержать никакой логики. В том числе такой, которая приводит к исключениям. В отличие от методов. Поэтому, если для изменения состояния объекта, необходимо провести какие-то дополнительные проверки, то нужно делать это методом.
Иначе мы придём к чему-то такому:
var foo = new Bar();
try
{
  foo.Name = "This is my Name";
}
catch (Exception ex)
{
    Console.WriteLine(ex);
}
> Коцептуально как раз тем, что свойства не должны содержать никакой логики.

Это неожиданно. А вы это где прочитали?
WPF и его Data Binding много чего странного привносит в архитектуру решения
В точку.
свойства не должны содержать никакой логики.
Судя по всему, вы путаете назначение полей и свойств. Свойства как раз для того и нужны, чтобы добавить некую «простую» логику, чего нельзя сделать для поля. Интуитивно я понимаю, что вы имели в виду. Если эта логика переиспользуется или слишком сложна (много строк или различающихся по смыслу действий?) для свойства, то ее имеет смысл вынести в отдельный (переиспользуемый) метод или даже в несколько.

В вашем примере, если Name — это свойство, при установке которого должны установится другие, то вполне ожидаемо получить исключение при ошибке парсинга или конвертации. Например, FullName может ожидать пробел и/или более одного слова, чтобы установить FirstName и LastName.
Я в целом согласен с Imbecile. В вашем примере я бы сделал FullName только с геттером, который бы бросал исключение, если FirstName или LastName не установлены (если нам надо). И отдельный метод ParseFullName.
Излишнее стремление к лаконичности — порочно. По мне, вот этот код:

public ClientService(
IClientsRepository clientsRepository,
IClientsNotifications clientsNotificator)
{
if (clientsRepository == null)
{
throw new ArgumentNullException(nameof(clientsRepository));
}
if (clientsNotificator == null)
{
throw new ArgumentNullException(nameof(clientsNotificator));
}

this.clientsRepository = clientsRepository;
this.clientsNotificator = clientsNotificator;
}


чище и понятнее вот этого:

public ClientService(
IClientsRepository clientsRepository,
IClientsNotifications clientsNotificator)
{
this.clientsRepository = clientsRepository ?? throw new ArgumentNullException(nameof(clientsRepository));
this.clientsNotificator = clientsNotificator ?? throw new ArgumentNullException(nameof(clientsNotificator));
}


Всем известно, что в C и C++ можно писать очень кратко и очень непонятно. Только надо ли?
По-моему второй первый вариант кажется приятней, только если редко сталкиваетесь с оператором ??.. Я часто использую/сталкиваюсь, поэтому мне второй вариант кажется лаконичнее.

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

Попробуйте перевести код на человеческий язык:


  1. Если аргументы не валидны, то бросить исключение.
    Сохранить аргументы
  2. Сохранить аргументы или бросить исключение, если они не валидны.

Второй вариант содержит меньше дублирования и читается легче (после пятиминутного привыкания)

Второй вариант семантически отличается от первого. В первом варианте — в случае возникновения исключений внутреннее состояние экземпляра не меняется. Тогда как во втором случае — изменяется и становится неконсистентным.

Это конечно же надо иметь в виду в случае исполнения вне конструктора.
Второй вариант семантически отличается от первого. В первом варианте — в случае возникновения исключений внутреннее состояние экземпляра не меняется. Тогда как во втором случае — изменяется и становится неконсистентным.

А вот это верно.
Контракты должны проверяться до действий, меняющих состояние.

Это конечно же надо иметь в виду в случае исполнения вне конструктора.
Да и в конструкторе тоже — что, если в нем выполняются некие «тяжелые» действия и/или с сайд эффектами, и вдруг окажется, что ничего делать не надо было, т.к. в параметрах пришел null?
(Что не отменяет того, что такие тяжелые конструкторы не следует делать.)
Вариант вида this.clientsRepository = clientsRepository ?? throw new ArgumentNullException(nameof(clientsRepository)); вполне чист и понятен.

Другое дело, что выбор примера неудачен, т.к. подобные вещи лучше делать через Code Contracts — жаль, что в System.Diagnostics.Contracts так и не стали в .NET стандартом (хотя бы де факто) и полноценной частью платформы.

К тому же, если совсем по хорошему, то тут и не Code Contracts нужны, а (Not)Nullability.
Я за Maybe и Either.
Они попросту принуждают невалидные ветки обрабатывать. можно, конечно, исхитриться и забить, но на это больше усилий требуется, чем на Nullable.
Sign up to leave a comment.

Articles

Change theme settings