Существует довольно распространенный шаблон, используемый в .NET для тестирования возможностей класса. Здесь я буду использовать класс Stream в качестве примера, но проблема относится ко всем классам, использующим этот шаблон.
Образец должен предоставить логическое свойство CanXXX, чтобы указать, что в классе имеется возможность XXX. Например, класс Stream имеет свойства CanRead, CanWrite и CanSeek, чтобы указать, что могут быть вызваны методы чтения, записи и поиска. Если значение свойства равно false, вызов соответствующего метода приведет к выдаче NotSupportedException.
Из документации MSDN в классе потока:
В зависимости от базового источника данных или репозитория потоки могут поддерживать только некоторые из этих возможностей. Приложение может запрашивать поток для своих возможностей, используя свойства CanRead, CanWrite и CanSeek.
И документация для свойства CanRead:
При переопределении в производном классе получает значение, указывающее, поддерживает ли текущий поток чтение.
Если класс, полученный из Stream, не поддерживает чтение, вызовы методов Read, ReadByte и BeginRead вызывают исключение NotSupportedException.
Я вижу много кода, написанного по следующим строкам:
if (stream.CanRead)
{
stream.Read(…)
}
Обратите внимание, что нет кода синхронизации, скажем, для блокировки объекта потока любым способом -— другие потоки могут обращаться к нему или к объектам, на которые он ссылается. Также не существует кода для обнаружения исключения NotSupportedException.
В документации MSDN не указано, что значение свойства не может меняться со временем. Фактически, свойство CanSeek изменяется на false, когда поток закрыт, демонстрируя динамический характер этих свойств. Таким образом, нет договорной гарантии того, что вызов Read() в приведенном выше фрагменте кода не будет вызывать исключение NotSupportedException.
Я ожидаю, что существует много кода, который страдает от этой потенциальной проблемы. Интересно, как к этому обратились те, кто идентифицировал этот вопрос. Какие шаблоны дизайна подходят здесь?
Я также благодарен за комментарии к действительности этого паттерна (пары CanXXX, XXX()). Для меня, по крайней мере, в случае класса Stream, это представляет класс/интерфейс, который пытается сделать слишком много и должен быть разделен на более фундаментальные элементы. Отсутствие жесткого документированного контракта делает тестирование невозможным и выполнение еще сложнее!