




I guess the guys that wrote IEnumerable … had a good feeling after creating [it].
Я не совсем уверен. Что касается интерфейсов, IEnumerable оказался провальным. Это очевидно из того факта, что IEnumerable<T>, его общий аналог, на самом деле предлагает полностью интерфейс разные. (Ну, «другой» - не очень хорошее слово; в основном, оно добавляет одноразовости и приуменьшает значение совершенно бесполезного метода Reset; однако этот метод, конечно, все еще присутствует.)
Кроме того, похожие языки, которые имеют похожие конструкции (на ум приходит Java), имеют гораздо лучшую, более расширяемую конструкцию для удовлетворения тех же потребностей (и даже больше). Например, итераторы Java могут быть расширены до двунаправленных или для изменения доступа (тогда как IEnumerable всегда доступен только для чтения).
Обновлено: Поскольку в комментариях так много противоречий, позвольте мне уточнить. Я не говорю, что IEnumerable (или IEnumerator) являются интерфейсами плохой. Они адекватные. Однако их можно было улучшить. По крайней мере, метод Reset кажется беспорядочным.
Terjet сказал, что он «использует [s] это все время» - и это в точности моя точка зрения! IEnumerable - это основной интерфейс всей платформы .NET. Это повсеместно. Без него не было бы .NET. Поэтому не слишком ли много просить об интерфейсе идеально?
«Адекватный» - это просто еще одно слово для обозначения неудач.
Возможно, я мог бы перефразировать свой вопрос, чтобы он касался IEnumerable <T> и IQueryable <T>. Я все еще думаю, что IEnumerable <T> очень полезен и успешен, потому что он так широко используется. (Я постоянно использую это)
В каком смысле IEnumerable <T> - это «совершенно другой интерфейс»? Это точно так же, только общее. И тогда единственная разница между IEnumerator <T> и IEnumerator заключается в том, что IEnumerator <T> расширяет IDisposable, что полезно для блоков итераторов.
(Конечно, помимо универсальности - и вы не можете винить дизайнеров IEnumerator в том, что универсальные шаблоны отсутствуют во фреймворке ...)
Я также не согласен с расширяемой природой - многие итераторы просто не подходят для того, чтобы быть доступными для записи или двунаправленными, и как только вы действительно не можете полагаться на полностью реализованный интерфейс, эти полуобязательные члены становятся намного менее полезными. (Даже сброс в IEnumerator был ошибкой IMO.)
@Jon: О разнице: см. Пояснение в моем сообщении. По сути, метод Reset был большой ошибкой. О расширяемости: я не могу говорить о Java, но C++ убедительно показывает, что итераторы могут быть реализованы лучше много.
Он не «обесценивает» Reset - он просто наследует его от IEnumerator, потому что ему не нужно изменять подпись. В противном случае вы должны применить ту же логику к MoveNext (), что очень важно! Утилизация - единственная реальная разница. Что касается итераторов C++ ... несколько иначе, IMO.
(Продолжение) Различия между универсальными шаблонами и шаблонами позволяют по-разному работать с идеями интерфейсов C++ и Java / C#. Я доволен IEnumerable <T> - кроме Reset, я думаю, что это хороший интерфейс.
Почему Reset () - такая плохая идея?
Во многих случаях это просто не имеет смысла - например, вы не можете сбросить поток данных, пришедший из сети (при условии, что вы не буферизовали все это).
Я согласен с Конрадом в этом. Конечно, полезно иметь итератор с прямым доступом, такой как IEnumerable, но он ограничен тем, что вообще не поддерживает более сложные двунаправленные или произвольные. Меня не впечатляет IEnumerable <T> (хотя установка LINQ поверх него очень удобна)
Возможны многие вещи (например, блоки итераторов). потому что IEnumerable настолько ограничен. Это не означает, что другие более сложные API-интерфейсы бесполезны - IList <T> и т. д. - но было бы сложно реализовать такие вещи, как LINQ и блоки итератора, поверх этих более сложных интерфейсов.
Джон, я согласен с вами, что LINQ не будет работать (или даже не понадобится) другой итератор. Я не это сказал. В C++ также есть алгоритмы, которые работают только с итераторами вывода (что в основном является моделями IEnumerable).
Так что должны быть разные интерфейсы для моделирования более способных итераторов - это нормально. Только не заставляйте меня генерировать исключения, потому что я не могу реализовать все - гораздо лучше иметь более простой интерфейс (например, IEnumerable). Короче говоря, я действительно не согласен с тем, чтобы назвать IEnumerable провалом.
«IEnumerable <T> меня не впечатляет» - я тоже, но я не думаю, что он сам по себе впечатляет. Он минималистичен, что придает ему универсальность, что делает его широко применимым, и все приложения, рассматриваемые вместе, впечатляют.
@ Джон, да. Конечно, я не предлагал набивать интерфейс IEnumerable большим количеством участников. Я просто не понимаю, как можно построить разумную иерархию поверх IEnumerable.
@Earwicker: Я, с другой стороны, бы впечатлен скупым интерфейсом. Я большой поклонник простоты. Однако IEnumerable + IEnumerator не очень просты.
@Konrad: есть другие интерфейсы, уже построенные на IEnumerable: IOrderedEnumerable, например, и IList и т. д. Другие интерфейсы также могут быть построены поверх IEnumerator, точно так же, как Java имеет ListEnumerator с дополнительной функциональностью. У меня такое чувство, что нам придется соглашаться, чтобы не соглашаться.
И последнее: когда «адекватный» и «неудачный» стали синонимами? Два определения: адекватный: «достаточный для цели»; сбой: «событие, которое не достигает своей предполагаемой цели». Извините, но они просто нет одинаковые.
Что бы вы удалили из IEnumerable + IEnumerator для упрощения? Большинство людей удалили бы Reset, но, безусловно, все остальное важно для концепции итерации. Он минимально отражает понятие прямого итератора.
@Earwicker: Совершенно верно. Сброса там быть не должно, и IEnumerator должен в первую очередь иметь расширенный IDisposable, но это все, что я бы изменил. В частности, я предпочитаю .NET-модель MoveNext () / Current модели Java hasNext () / next (). По моему опыту, с ним легче работать.
Я доволен дизайном интерфейса, лежащего в основе Нажмите LINQ. Это очень простой интерфейс, но с ним вы можете делать самые разные интересные вещи. Вот определение (по памяти, но, по крайней мере, будет довольно близко):
public interface IDataProducer<T>
{
event Action<T> DataProduced;
event Action EndOfData;
}
По сути, он позволяет наблюдателям «прослушивать» поток данных, а не «извлекать» из него, как работает IEnumerable.
Три интересных места:
Удобно, что он успешно работает с дисперсией в C# 4.0 (я недавно проверял CTP ;-p). Хорошая работа, мы не ошиблись и добавили в интерфейс метод «протолкнуть».
Рассматривали ли вы последствия отказа от использования делегатов EventHandler для безопасности? например Если ваш код имеет полное доверие, то код с частичным доверием может зарегистрировать Environment.FailFast в качестве обработчика для IDataProducer <string> .DataProduced и запустить его в контексте полного доверия.
Я не думал об этом, но если они работают в контексте полного доверия, не могли бы они просто назвать это сами?
@Greg: Подумав об этом в одночасье, я думаю, что могу понять, что вы имеете в виду. Думаю, я бы изменил это, если бы это когда-нибудь стало проблемой, но пока все в порядке.
Собственно интерфейс? Или просто API? Я доволен тем, как сработал материал универсальный оператор (доступен здесь) - я регулярно вижу, как люди спрашивают об использовании операторов с дженериками, поэтому я рад, что это удобный ответ для многих людей. Это может быть немного проще в C# 4.0, но я очень сомневаюсь, что он будет таким же быстрым - DLR-дерево / динамический материал имеет накладные расходы.
Я также очень рад, что это было полезно в Push LINQ, о котором уже упоминал Джон ;-p
Как и Марк, я не уверен, действительно ли это имеет значение для этого вопроса, но я могу определенно подтвердить, что его общий операторский материал потрясающий :)
Я работаю над системой проверки, которую планирую вскоре выпустить в сообщество. По сути, это реализация шаблона Спецификация.
Основной интерфейс спроектирован так, чтобы быть функциональным по своей природе:
public interface ICheckable<T>
{
CheckResult Apply(T target);
}
CheckResult - это struct, представляющий значение трех состояний: Passed, Failed и Ignored. Все преобразования и перегрузки операторов используются для обработки его как значения Boolean.
Это позволяет валидаторам выражать «У меня нет мнения» вместо того, чтобы возвращать вводящее в заблуждение значение true (подумайте, что RangeValidator, указывающий, что пустое поле является допустимым, поэтому он хорошо работает с RequiredFieldValidator).
Композиция происходит естественно и выполняется с помощью статических классов а-ля Linq. Каждая точка в проверке является неявным And следующей операции:
public static ICheckable<T> Add<T>(this ICheckable<T> check, ICheckable<T> otherCheck)
{
return new Check<T>(t => check.Apply(t) && otherCheck.Apply(t));
}
public static ICheckable<T> Either<T>(this ICheckable<T> check, ICheckable<T> firstCheck, ICheckable<T> secondCheck)
{
return check.Add(t => firstCheck.Apply(t) || secondCheck.Apply(t));
}
public static ICheckable<T> Not<T>(this ICheckable<T> check, ICheckable<T> negatedCheck)
{
return check.Add(t => !negatedCheck.Apply(t));
}
Методы расширения работают нормально:
public static ICheckable<int> Percentage(this ICheckable<int> check)
{
return check.Add(n => n >= 0 && n <= 100);
}
public static ICheckable<T> GreaterThanOrEqualTo<T>(this ICheckable<T> check, T value) where T : IComparable<T>
{
return check.Add(t => t.CompareTo(value) >= 0);
}
public static ICheckable<T> LessThanOrEqualTo<T>(this ICheckable<T> check, T value) where T : IComparable<T>
{
return check.Add(t => t.CompareTo(value) <= 0);
}
public static ICheckable<T> Range<T>(this ICheckable<T> check, T minimum, T maximum) where T : IComparable<T>
{
return check.GreaterThanOrEqualTo(minimum).LessThanOrEqualTo(maximum);
}
// RangeExcludeMinimum
// RangeExcludeMaximum
// RangeExclusive
Каждая операция включает в себя перегрузки, которые используют лямбду для проверки:
public static ICheckable<T> Add<T>(this ICheckable<T> check, Func<ICheckable<T>, ICheckable<T>> makeCheck)
{
return check.Add(makeCheck(new IgnoredCheck<T>()));
}
Таким образом, вы можете использовать такой синтаксис:
ICheckable<int> check;
check.Add(i => i.Percentage().GreaterThan(50).Even());
По какой-то причине Apply не называется Check? Это то, чего я ожидал, исходя из результата и имени интерфейса.
Ха-ха, хороший улов :-) Интерфейс изначально был ICheck <T>, но я переименовал его, чтобы отразить его отложенный характер. Я, наверное, реализую это предложение.
Это интерфейс ActionScript 3, который был ядром нашего нового поведения Flash Player в as3.
public interface IDisposable {
public function dispose():void;
}
Как и следовало ожидать, метод dispose должен закрыть все ресурсы и отбросить все ссылки на все, что возможно.
Программисты на C++ могут посмеяться над этим «инновационным» интерфейсом (что вполне справедливо), но as3 представил множество проблем, связанных с управлением памятью во Flash. Все эти проблемы являются «старой шляпой» для многих компилируемых языков, но программисты ActionScript только сейчас впервые сталкиваются с этими проблемами.
Да, это все еще язык со сборкой мусора. Но, что бы там ни было, «держаться за руки» гораздо меньше, чем в ActionScript 2, о чем свидетельствует необходимость в этом интерфейсе.
Для работы с MVP-шаблоном у меня есть несколько базовых интерфейсов фреймворка:
public interface IValidatable {
bool IsValid { get; set; }
void ShowValidationFailureMessage(string message);
}
public interface ISubmitable {
event EventHandler Submit;
void ShowSubmitFailureMessage(string message);
void ShowSubmitSuccessMessage(string message);
}
public interface ICancelable {
event EventHandler Cancel;
}
С помощью этих трех интерфейсов я могу написать докладчика, который имеет эти общие операции (которые охватывают в основном все операции с формами). Например:
public interface ILogin : IValidatable, ISubmitable, ICancelable {
string Username { get; set; }
string Password { get; set; }
}
Затем вы можете создать докладчика и убрать его.
Похоже, это хороший кандидат на статус Community Wiki.