F>ConvertionResult<int> result = int.TryParse( "asdf" );
F>if( !result.IsValid )
F>{
F> Console.WriteLine("It was not an integer number");
F> return;
F>}
F>Console.WriteLine( result.Value );
F>
И чем это принципиально лучше варианта с out? По мне твой вариант и вариант с out по читабельности равнозначны. Но в общем случае out'ы гибче, например, если понадобиться возвращать также сообщение об ошибке, то в варианте с out будет достаточно добавить перегрузку bool TryParse(out int result, out string error), а при твоем подходе придется писать класс ConvertionResultWithError<T>. Иногда создание дополнительного класса оправдано, конечно, но далеко не всегда. И вот для тех случаев, когда дополнительный класс себя не оправдывает, out'ы в шарпе очень полезное и удачное решение, которое никак не заслуживает оценки code-smell.
Здравствуйте, Undying, Вы писали:
U>Важное достоинство out, он полностью отвечает принципу самодокументируемости кода. Результат функции с out'ами понятен, даже без чтения документации.
U>
U>bool Read(out string dataBuffer, out uint remainDataCount);
U>
U>А вот если ее переписать на тупл:
U>
U>Tuple<bool, string, uint> Read();
U>
U>То без поллитры определить, что значит, к примеру, uint будет затруднительно.
Ты пытаешься писать больше в процедурном ключе. В объектном все-таки правильнее будет выделить класс Stream, где будут поля Size, Position, ...
Здравствуйте, Undying, Вы писали:
U>Какой код на C# 3.5 ты пишешь вместо int.TryParse(text, out value)?
Начнем с того, что я функцией TryParse вообще стараюсь не пользоваться. Вылет исключения при попытке распарсить что-то меня обычно устраивает. Далее, если по-простому, то вместо функции "Try-что-нибудь", мне обычно нужна функция "Can-что-нибудь" в совокупности с функцией "Do-что-нибуть". Хотя на практике у меня не возникало необходимости использовать парсинг таким образом, я бы сделал так: завел бы функцию "CanParse", оборачивающую TryParse и использовал бы её в совокупности с Parse.
Выглядело бы это так:
var myInt = int.CanParse(MyString) ? int.Parse(MyString) : DefaultBusinessLogicAcceptableIntValue;
или
if(!int.CanParse(MyString))
{
throw new ...
}
var myInt = int.Parse(MyString);
Казалось бы, разница невелика, но:
1. переменная myInt всегда будет проинициализирована ТОЛЬКО корректным распарсенным значением. Там никогда не будет "дефолтового" нуля, который случайно может попасть в дальнейший код, как легальное значение.
2. этот вариант удобнее тем, что функция возвращает значение только как результат. В итоге ее удобнее комбинировать с другими функциями через функции высшего порядка (тот же Linq). Например:
var myInts = MyStringsCollection.Where(int.CanParse).Select(int.Parse);
C TryParse этот код будет таким:
IEnumerable<int> ParseStrings(IEnumerable<string> strings)
{
foreach(var i in strings)
{
int result;
if(int.TryParse(i, out result))
{
yield return result;
}
}
}
...
var mySrtings = ParseStrings(strings);
Неуклюже, правда?
Недостаток этого подхода — два захода в алгоритм парсинга, что м.б. неприемлемо по соображениям перформанса.
Преимущество — не вводятся новые сущности, позволяющие хранить и обрабатывать опциональные результаты.
Улучшить перформанс можно путем применения этих сущностей.
Например, это могло бы выглядеть так (реализация optional тут взята с потолка, метод Unwap принимает функцию, которую надо вызвать, если значения нет):
var myInt = int.OptionalParse(MyString).Unwrap(() => throw new ...);
и для коллекций:
var myInts = MyStringsCollection.Select(int.OptionalParse).Select(_ => _.Unwrap(() => throw new ...));
При этом метод парсинга вызывается один раз и перформанс не страдает.
Вариантов реализации того, во что и как заворачивать опциональный результат функции — тысяча и один, но принцип у них всех похож.
Здравствуйте, Mystic, Вы писали:
M>Ты пытаешься писать больше в процедурном ключе. В объектном все-таки правильнее будет выделить класс Stream, где будут поля Size, Position, ...
Правильно писать в ключе наиболее подходящем для решения задачи. Обычно наилучшим подходом для решения задачи является смесь объектного и процедурного стиля.
Здравствуйте, Undying, Вы писали:
U>... U>То без поллитры определить, что значит, к примеру, uint будет затруднительно.
С этим трудно поспорить. Замечу лишь, что туплы с небольшим количеством значений обычно трудностей не вызывают.
В приведенном примере, скорее всего, возвращался бы тип Optional<Tuple<string, uint>>, т.е. тупл состоял бы из двух типов. Если же надо возвращать что-то бОльшее, то можно и класс под это дело завести. 25 значений и через out-параметры плохо пролезут.
Здравствуйте, MozgC, Вы писали:
MC>Цитирую из книги Framework Design Guidelines:
Обожаю такую аргументацию:
...requires experience...
...understandin...
...is not widely understood...
Извините, это инженеры, или кто? Если человек не имеет экспириенса и/или не понимает, что это за рефауты такие, и не может за пять минут разобраться с гуглем в руках, думаете, он напишет код лучше, если не провоцировать его на их использование? Лох — это судьба, говорили в городе, где я прожил много лет (на третьем месте в криминальном рейтинге городов России). Перефразируя, если человек тупой, он найдет, где ему поиметь свой факап. Без рефов и аутов. Методом проб и ошибок я пришел к выводу, что не надо а) работать на тупых менеджеров б) давать работу тупым менеджерам/инженерам. Вот и все.
Что касается исходного тезиса ("дело нехорошее"). Пока в языке нет возможности возвращать более одного значения, код с рефаутами остается наиболее элегантным, если потенциальный новый тип не нужен сам по себе (используется только для заворачивания аргументов).
SV.>Извините, это инженеры, или кто? Если человек не имеет экспириенса и/или не понимает, что это за рефауты такие, и не может за пять минут разобраться с гуглем в руках, думаете, он напишет код лучше, если не провоцировать его на их использование? Лох — это судьба, говорили в городе, где я прожил много лет (на третьем месте в криминальном рейтинге городов России). Перефразируя, если человек тупой, он найдет, где ему поиметь свой факап. Без рефов и аутов. Методом проб и ошибок я пришел к выводу, что не надо а) работать на тупых менеджеров б) давать работу тупым менеджерам/инженерам. Вот и все. SV.>Что касается исходного тезиса ("дело нехорошее"). Пока в языке нет возможности возвращать более одного значения, код с рефаутами остается наиболее элегантным, если потенциальный новый тип не нужен сам по себе (используется только для заворачивания аргументов).
Начну с того, что процитированные выше правила взяты из книги Framework Design Guidelines, т.е. они больше подходят для разработки публичных API. При разработке публичных API/фреймворков нужно руководствоваться определенными принципами, например:
— стремиться к простоте
— обеспечить низкий входной порог программистов
— разрабатывать библиотеку для широкого круга программистов
— затруднить неправильное использование API
и т.д.
Параметры out и ref не очень способствуют выполнению этих принципов. Я практически уверен, что usability studies, которые необходимо проводить при разработке публичных библиотек, покажут что новичкам сложнее понимать методы работающие с ref и out параметрами, а наличие ref и out в языке будет способствовать неправильному их применению (конкретно это доказано уже много раз). Повторюсь, ref и out — не зло, но они могут мешать приведенным выше принципам разработки публичных библиотек и пользования языком в целом.
Здравствуйте, MozgC, Вы писали:
MC>Параметры out и ref не очень способствуют выполнению этих принципов. Я практически уверен, что usability studies, которые необходимо проводить при разработке публичных библиотек, покажут что новичкам сложнее понимать методы работающие с ref и out параметрами, а наличие ref и out в языке будет способствовать неправильному их применению (конкретно это доказано уже много раз). Повторюсь, ref и out — не зло, но они могут мешать приведенным выше принципам разработки публичных библиотек и пользования языком в целом.
Ну и что? Дай дураку хрустальный хрен — он и хрен сломает, и руки изрежет. Посмотрите на COM. Там это решено очень просто: в основе лежит близкий к корням механизм указателей, которые видны в плюсах as is, но в VB [retval] превращался в возвращаемое функцией значение (а исходный HRESULT — в тыкв... то есть, в исключение). Кесарю — кесарево, ВэБэшнику — изиотический синтаксис. Почему бы не ввести еще какой-нибудь язык, типа, Dummy.Net, в котором исходное API с рефаутами выглядело бы как без рефаутов? И овцы сыты, и волки целы.
Здравствуйте, Undying, Вы писали:
U>И чем это принципиально лучше варианта с out? По мне твой вариант и вариант с out по читабельности равнозначны.
Я говорил что вариант принципиально лучше? Ты просил привести кардинально лучший вариант? Нет, просто "как это можно".
Варианты именно равнозначны, т.е. без ref/out можно, при желании, обойтись, если использовать объектно-ориентированный подход. ref/out — наследие процедурного подхода в программировании.
Я, правда, не призываю от них отказываться — к параноидальному стремлению к "чистоте подхода" не склонен. Особенно учитывая, что есть задачи, где ref/out действительно очень полезны. Но это не TryParse, а перевод на C# старого кода с процедурных языков или интеграция с нативным кодом на таких языках.
U>Но в общем случае out'ы гибче, например, если понадобиться возвращать также сообщение об ошибке, то в варианте с out будет достаточно добавить перегрузку bool TryParse(out int result, out string error), а при твоем подходе придется писать класс ConvertionResultWithError<T>.
Либо изменить сам класс, добавив к нему поле. Либо сделать наследника.
При этом, что характерно, можно оставить сигнатуту метода без изменения, и все те места, где использовалась старая функция останутся без изменения. А там где потребуется — можно будет проверить и текст тоже.
Так что вполне равнозначное решение.
U>Иногда создание дополнительного класса оправдано, конечно, но далеко не всегда.
Не вижу причин, по которым нельзя создать еще один (или не один) класс.
Methods that implement the Try<Something> pattern, such as Int32TryParse()()(), do not fire this violation. The following example shows a structure (value type) that implements the Int32TryParse() method...