Класс .NET Stream плохо спроектирован? - PullRequest
18 голосов
/ 11 ноября 2009

Я потратил довольно много времени на знакомство с классами .NET Stream. Обычно я многому учусь, изучая дизайн класса профессиональных, коммерческих платформ, но я должен сказать, что что-то здесь не совсем пахнет.

System.IO.Stream - это абстрактный класс, представляющий последовательность байтов. Имеет 10 абстрактных методов / свойств: Read, Write, Flush, Length, SetLength, Seek, Position, CanRead, CanWrite, CanSeek. Из-за большого количества абстрактных членов становится затруднительным извлекать из-за того, что вам нужно переопределить все эти методы, даже если большинство из них просто бросают NotImplemented.

Ожидается, что пользователи классов Stream будут звонить CanRead, CanWrite или CanSeek, чтобы выяснить возможности Stream, или, я полагаю, просто пойти дальше и вызвать Read, Write или Seek и посмотреть, если он бросает NotImplemented. Это только у меня так, или это вялый дизайн?

Хотя есть много гнид, которые я хотел бы выбрать с дизайном класса Stream, основной вопрос, о котором я хотел бы спросить, заключается в следующем: почему они не использовали интерфейсы, такие как IReadable, IWriteable, ISeekable, вместо этого? Тогда новый класс Stream может изящно наследоваться от интерфейсов, которые он поддерживает. Разве это не объектно-ориентированный способ ведения дел? Или я что-то упустил?

Обновление : было отмечено, что значение CanRead и др. может измениться во время выполнения - например, если FileStream закрыто - и точка взята. Тем не менее, я не уверен, что это хороший дизайн. Оттуда, откуда я, попытка чтения из файла, который уже был закрыт, является ошибкой или, по крайней мере, исключительным условием. (И, таким образом, исключение является естественным способом справиться с этой ситуацией.)

Значит ли это, что каждый раз, когда я собираюсь Read из Stream, я должен проверять CanRead? И будет ли это означать, что я должен поставить блокировку на место, чтобы избежать состояния гонки, если возможно, что значение когда-нибудь изменится между вызовом CanRead и вызовом Read?

Обновление от 7 августа 2010 г. : Похоже, консенсус здесь заключается в том, что дизайн Stream довольно хорош в его нынешнем виде. Но позвольте мне еще раз спросить, чтобы быть на 100% уверенным: люди пишут что-то подобное каждый раз, когда читают из потока?

//  s is a Stream

lock(s)
{
    if (s.CanRead)
    {
        s.Read(buf, 0, buf.Length);
    }
}

Ответы [ 8 ]

10 голосов
/ 11 ноября 2009

Я думаю, что классы разработаны хорошо. Я бы предпочел проверить свойство, а затем попытаться что-то сделать и поймать исключение. Интерфейсы терпят неудачу в случае потоковых типов, которые имеют несколько «типов». Какой тип будет возвращен из метода, который дает вам читаемый и доступный для записи поток? Я согласен, что дизайн не является настоящим объектно-ориентированным дизайном, но вы действительно хотите обрабатывать потоки таким образом? Что может произойти в этом случае, если некоторые свойства могут измениться, если поток будет закрыт или что-то еще изменится?

Я думаю, что этот вопрос поднимает действительно интересный эксперимент, почему бы не попробовать создать свои собственные классы, связанные с потоком. Опубликуйте свой редизайн в CodePlex или Google Code, это будет отличным опытом обучения и приведет к созданию потенциально полезной библиотеки для использования другими.

9 голосов
/ 11 ноября 2009

Использование интерфейсов будет означать, что значение «CanRead» не может быть изменено во время выполнения. Класс «FileStream» изменяет свойство «CanRead» в зависимости от текущего состояния файла.

7 голосов
/ 11 ноября 2009

Они, вероятно, не использовали интерфейсы, потому что в то время не было методов расширения. Если вы хотите, чтобы у каждого потока были такие вещи, как метод ReadByte по умолчанию, вам нужно было использовать класс.

Я написал в блоге пару месяцев назад о том, почему мне не нравится IO.Stream и что я должен сделать. По сути, это сводится к потокам, не очень безопасным для типов.

5 голосов
/ 11 ноября 2009

Интерфейсы могут быть перегружены, и это может быть одним из таких случаев. Я думаю, что текущий дизайн великолепен. Тот факт, что потоки могут изменять возможности во время выполнения, означает, что IReadable / IWritable / ISeekable не избавит от необходимости использования CanRead, CanWrite и CanSeek, так что вы просто увеличите сложность без какой-либо реальной выгоды, кроме устранения нескольких методов и свойств заглушки в ваших производных классах.

Лично я предпочитаю, чтобы потоковый класс был проще в использовании, чем в написании, потому что вы будете писать его один раз и использовать его много раз.

4 голосов
/ 11 ноября 2009

Класс Stream использует опциональный шаблон функции, вы можете прочитать больше здесь .

3 голосов
/ 20 сентября 2012

Чтобы ответить на вопрос: Вероятно.

Я в значительной степени согласен с ответом @ Strilanc о том, что Да, он плохо реализован, но я подумал, что я тоже смогу поделиться своими мыслями.

Хотя, конечно, было бы чётче реализовать этот материал с помощью компонуемых интерфейсов и методов расширения (теперь, когда они доступны), в .NET 1 не было этих функций, поэтому я могу понять, почему они решили разработать Поток таким образом.

Однако теперь, когда у нас есть действительно полезные конструкции, такие как Generics и методы расширения, я думаю, что пришло время пересмотреть многие оригинальные классы и пометить их ObsoleteAttribute . Конечно, сначала вы захотите заменить API.

ObsoleteAttribute позволит этим классам оставаться как часть платформы, одновременно препятствуя их использованию. Эти классы могут быть удалены в будущей версии платформы или даже сохранены, но недоступны в другом профиле платформы.

Это применимо ко ВСЕМ неуниверсальным коллекциям (не только к тем, которые находятся в пространстве имен System.Collections ) и к "странным абстракциям". System.Text.RegularExpressions * Классы коллекций являются хорошими примерами, но их так много во всей среде.

Я знаю, что мой комментарий о "странных абстракциях" чрезвычайно расплывчат, но я думаю, что лакмусовая бумажка могла бы заключаться в том, что если вам придется генерировать исключения NotImplementedException во всех ваших производных классах, возможно, есть лучший способ сделать что-либо.

Итак, в общем, мне не нравится дизайн Stream , но пока нет лучшего API, вам, вероятно, придется с этим справиться. Написание адаптеров / оболочек для существующего Stream , вероятно, является лучшим выбором на данный момент, и это то, чем я сейчас занимаюсь.

1 голос
/ 11 ноября 2009

Можно использовать (Java *) подход к написанию в основном пустого класса MyStream, который наследует базовый класс Stream, но предоставляет большинство методов-членов (например, CanSeek()), и разумно их вводит. поведение по умолчанию (например, бросание NotImplemented). Тогда ваш реальный класс просто расширяет ваш MyStream класс, реализуя два или три оставшихся метода, которые вам действительно нужны.

Повторно используя класс MyStream, вы сэкономите массу изобретений колеса.

* Это называется абстрактный адаптер класс в библиотеках Java.

0 голосов
/ 06 сентября 2010

Моя проблема с классом потока - это свойство Length - оно не определяет, как реализовать поток неизвестной, неопределенной или бесконечной длины!

...