Comments 24
Это как то стрёмно =)
У DelayedOk и DelayedError нет никакой информации о втором возвращаемом типе и в итоге они могут приводиться так же неявно к любой комбинации и своего типа и любого другого:
Result<string, int> example = new DelayedOk<string>("123");
Не знаю, правда, мешает ли это хоть в чём то, на простых примерах в статье - да, смотрится удобно. Стоит ли оно того - хз =)
Если это генерируемый код - вроде какая разница, генерировать с явным типом или оставлять выведение на компилятор. Если пишется руками - то IDE давно уж отлично подсказывает return new Result<string, string>
как раз зная возвращаемый результат =)
UPD: плюсик статье воткнул, в целом забавно всё равно =)
Интересное замечание! Но конечно эти промежуточные типы не предполагают по своему смыслу ручного использования. И это хорошо бы ограничить. Например, натянув ещё одну промежуточную прокладку неявных преобразований:
public readonly struct DelayedOk<T>
{
public T Value { get; }
private DelayedOk(T value) => Value = value;
public static implicit operator DelayedOk<T>(T value) => new(value);
}
public readonly struct DelayedError<T>
{
public T Value { get; }
private DelayedError(T value) => Value = value;
public static implicit operator DelayedError<T>(T value) => new(value);
}
public static class Result
{
public static DelayedOk<TOk> Ok<TOk>(TOk ok) => ok;
public static DelayedError<TError> Error<TError>(TError error) => error;
}
Отсюда и оценка статьи — небезынтересно, но описанный метод следует применять с осторожностью.
А разве тем кто читает IDE не подсказывает тип?
Явное указание типов при чтении мешает.
В коде
var employees = GetEmployeesByDepartment(departmentId);
прекрасно видно бизнес-смысл переменной -- список сотрудников. Какая мне разница, что там за тип: List<Employee>
, Employee[]
или вообще Dictionary
?
Вот если потребуется глубокое погружение в код с целью его модификации (что уже не чтение, а изучение), то тип подскажет IDE.
Мешает для простых типов. У нас в команде есть правило, что для простых значимых типов, а также string, DateTime, Guid и некоторых других нельзя использовать var. Классический пример - есть какое-то математическое выражение: var result = Какая-то формула с вызовом несколькими вызовами Math.*. На рецензировании тяжело определить какой тип в итоге у result и совпадает ли он с предполагаемым автором. Ошибки округления тут точно будут.
Какая мне разница, что там за тип: List, Employee[] или вообще Dictionary?
Хороший пример: в нем, если нет разницы, то явное указание IList<Emloyee> (или вообще IEnumerable<Emloyee>) вместо var подскажет, как именно автор кода собирается с этим работать дальше, т.е. какие именно методы следует ожидать увидеть в дальнейшем. Ну а Dictionary тут вообще не сильно похоже на два предыдущих.
PS Для меня обычно чтение=изучение, не обязательно с целью модификации: иногда надо просто понять, что именно делает код, потому как документация не идеальна.
С другой стороны, c var возможны внезапные сюрпризы. Например, если мы видим в коде настройки контейнера сервисов на ASP.NET что-то типа serviceCollection.что-то-там.AddOptions() (где serviceCollection имеет тип IServiceCollection), то занчение выражения остается имеющим тип IServiceCollection, а если мы видим очень похожее на него выражение serviceCollection.что-то-там.AddOptions<T>() то внезапно тип меняется на OptionsBuilder<T>. Сюрприз!
подскажет, как именно автор кода собирается с этим работать дальше, т.е. какие именно методы следует ожидать увидеть в дальнейшем
Хм, а зачем нужно предсказывать будущее? Зачем чего-то ожидать?
В процессе дальнейшего чтения я увижу, какие методы вызываются.
надо просто понять, что именно делает код
Вот для этого точное знание типов вообще не нужно. Достаточно понимать бизнес-смысл происходящего.
Вот для этого точное знание типов вообще не нужно. Достаточно понимать бизнес-смысл происходящего.
Ну, если вам действительно достаточно приблизительно понимать бизнес-смысл, а не как именно он реализуется — то таки да, детали реализации вам не нужны.
Но так можно проглядеть и что-то существенное — особенно если код писал «творец».
PS Я там в предыдущем комментарии кое-что вспомнил, и немного подправил — про то, как тип длинного выражения, записанного через точку может внезапно поменяться — и глядя на внезапно появившиеся методы, которых быть тут не должно, у читающего возникает когнитивный диссонанс.
Идея статьи интересная, и возможно даже имеет место применения, но конкретные примеры я бы скорее всего не использовал. Option<T> для случаев когда T - структура полностью заменяется Nullable<T>
int? Parse(string val)
=> int.TryParse(val, out var result)
? result
: null;
Result<TOk, TError> уже выглядит поинтересней, но конкретно финальная реализация немного смущает тем, что static class Result начинает возвращать не Result<TOk, TError>, а DelayedOk, что не всегда позволяет его использовать как create-метод, так как нельзя будет сделать например такое
var result = Result.Ok(1);
result = Parse("1");
а нужно будет явно объявлять
Result<int, int> result = Result.Ok(1);
result = Parse("1");
Но повторюсь: скорее всего такой финт ушами действительно может пригодится, просто в менее распространенных задачах.
Проблема в том, что тернарный оператор в C# не приводит ветви к "общему знаменателю"
Начиная с C# 9.0 явное приведение типа не требуется.
Как раз в конфигурации с DelayedResult<T>
код:
public static Result<int, string> ParseToResult(string input)
{
return int.TryParse(input, out var value)
? Result.Ok(value)
: Result.Error("Invalid value");
}
не скомпилится
Когда же мы разделяем отложенную инициализацию на два состояния DelayedOk
и DelayedError
, неявное приведение работает:
начинаешь читать
"Мне по-настоящему нравитЬся больше чего-либо в разработке ПО делать фреймворки ..."
прекращаешь читать
В C++ есть аналогичный трюк с аналогичной реализацией: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Return_Type_Resolver
Simulating Return Type Inference in C#