Когда уместно генерировать исключение из получателя или установщика свойства? Когда это не подходит? Зачем? Ссылки на внешние документы по этой теме были бы полезны ... Google оказался на удивление мало.
Когда уместно генерировать исключение из получателя или установщика свойства? Когда это не подходит? Зачем? Ссылки на внешние документы по этой теме были бы полезны ... Google оказался на удивление мало.
Ответы:
У Microsoft есть свои рекомендации по проектированию свойств на http://msdn.microsoft.com/en-us/library/ms229006.aspx.
По сути, они рекомендуют, чтобы получатели свойств были облегченными средствами доступа, которые всегда можно безопасно вызывать. Они рекомендуют переделывать геттеры в методы, если вам нужно выбросить исключения. Для установщиков они указывают, что исключения являются подходящей и приемлемой стратегией обработки ошибок.
Для индексаторов Microsoft указывает, что как геттеры, так и сеттеры допустимо генерировать исключения. Фактически, это делают многие индексаторы в библиотеке .NET. Наиболее частым исключением является ArgumentOutOfRangeException
.
Есть несколько довольно веских причин, по которым вы не хотите создавать исключения в методах получения свойств:
obj.PropA.AnotherProp.YetAnother
- при таком синтаксисе становится проблематичным решить, куда вводить операторы исключения исключения.В качестве примечания следует помнить, что то, что свойство не предназначено для создания исключения, не означает, что этого не произойдет; это может легко вызвать код, который это делает. Даже простое выделение нового объекта (например, строки) может привести к исключениям. Вы всегда должны писать свой код с осторожностью и ожидать исключений от всего, что вы вызываете.
Нет ничего плохого в том, чтобы генерировать исключения из сеттеров. В конце концов, какой лучший способ указать, что значение недействительно для данного свойства?
Для геттеров это обычно не одобряется, и это можно довольно легко объяснить: геттер свойства, как правило, сообщает текущее состояние объекта; таким образом, единственный случай, когда геттер имеет смысл выбросить, - это когда состояние недействительно. Но также обычно считается хорошей идеей проектировать свои классы таким образом, чтобы просто невозможно было изначально получить недопустимый объект или перевести его в недопустимое состояние обычными средствами (т.е. всегда обеспечивать полную инициализацию в конструкторах и попробуйте сделать методы безопасными в отношении исключений в отношении допустимости состояния и инвариантов класса). Пока вы придерживаетесь этого правила, ваши геттеры никогда не должны попадать в ситуацию, когда они должны сообщать о недопустимом состоянии и, следовательно, никогда не бросать.
Есть одно известное мне исключение, и оно на самом деле довольно серьезное: реализация любого объекта IDisposable
. Dispose
специально предназначен как способ привести объект в недопустимое состояние, и в этом случае даже есть специальный класс исключения ObjectDisposedException
. Совершенно нормально бросать ObjectDisposedException
из любого члена класса, включая средства получения свойств (и исключая его Dispose
самого), после того, как объект был удален.
IDisposable
должны стать бесполезными после a Dispose
. Если для вызова члена потребуется использовать ресурс, который Dispose
стал недоступным (например, член будет читать данные из потока, который был закрыт), член должен выдавать, ObjectDisposedException
а не утечку, например ArgumentException
, но если у кого-то есть форма со свойствами, которые представляют значений в определенных полях, было бы гораздо полезнее разрешить чтение таких свойств после удаления (с получением последних введенных значений), чем требовать ...
Dispose
должно быть отложено до тех пор, пока не будут прочитаны все такие свойства. В некоторых случаях, когда один поток может использовать блокирующее чтение объекта, в то время как другой его закрывает, и когда данные могут поступать в любое время до Dispose
этого, может быть полезно Dispose
отключить входящие данные, но разрешить чтение ранее полученных данных. Не следует навязывать искусственное различие между ситуациями Close
и Dispose
в тех случаях, когда в противном случае ничего не существовало бы.
Get...
метод. Исключением является ситуация, когда вам необходимо реализовать существующий интерфейс, который требует от вас предоставления свойства.
Это почти никогда не подходит для геттера, а иногда подходит для сеттера.
Лучшим источником ответов на подобные вопросы является «Руководство по разработке каркаса» Квалины и Абрамса; он доступен в виде переплетенной книги, и большие его части также доступны в Интернете.
Из раздела 5.2: Дизайн недвижимости
ИЗБЕГАЙТЕ исключения исключений из методов получения свойств. Получатели свойств должны быть простыми операциями и не должны иметь предварительных условий. Если получатель может генерировать исключение, его, вероятно, следует переделать в метод. Обратите внимание, что это правило не применяется к индексаторам, где мы ожидаем исключений в результате проверки аргументов.
Обратите внимание, что это правило применимо только к получателям свойств. Можно генерировать исключение в установщике свойств.
ObjectDisposedException
после того, как объект был Dispose()
вызван и что-то впоследствии запрашивает значение свойства? Похоже, что руководство должно быть таким: «Избегайте генерирования исключений из методов получения свойств, если только объект не был удален, и в этом случае вам следует рассмотреть возможность создания исключения ObjectDisposedExcpetion».
Один из хороших подходов к Исключениям - использовать их для документирования кода для себя и других разработчиков следующим образом:
Исключения должны быть для исключительных состояний программы. Это значит, что их можно писать где угодно!
Одна из причин, по которой вы можете захотеть поместить их в геттеры, - это документировать API класса - если программное обеспечение выдает исключение, как только программист пытается использовать его неправильно, он не будет использовать его неправильно! Например, если у вас есть проверка во время процесса чтения данных, может не иметь смысла иметь возможность продолжить и получить доступ к результатам процесса, если в данных были фатальные ошибки. В этом случае вы можете захотеть получить выходной бросок, если были ошибки, чтобы гарантировать, что другой программист проверит это условие.
Это способ документировать предположения и границы подсистемы / метода / чего угодно. В общем случае ловить их не следует! Это также связано с тем, что они никогда не выбрасываются, если система работает вместе ожидаемым образом: если происходит исключение, оно показывает, что предположения фрагмента кода не выполняются - например, он не взаимодействует с окружающим миром таким образом изначально это было предназначено. Если вы поймаете исключение, которое было написано для этой цели, это, вероятно, означает, что система вошла в непредсказуемое / несогласованное состояние - это может в конечном итоге привести к сбою или повреждению данных или тому подобному, что, вероятно, будет намного сложнее обнаружить / отладить.
Сообщения об исключениях - это очень грубый способ сообщения об ошибках - они не могут собираться массово и действительно содержат только строку. Это делает их непригодными для сообщения о проблемах во входных данных. При нормальной работе сама система не должна переходить в состояние ошибки. В результате сообщения в них должны быть предназначены для программистов, а не для пользователей - ошибки во входных данных могут быть обнаружены и переданы пользователям в более подходящих (настраиваемых) форматах.
Исключение (ха-ха!) Из этого правила - это такие вещи, как ввод-вывод, где исключения не находятся под вашим контролем и не могут быть проверены заранее.
Все это задокументировано в MSDN (как указано в других ответах), но вот общее практическое правило ...
В установщике, если ваше свойство должно быть проверено выше и выше типа. Например, свойство с именем PhoneNumber, вероятно, должно иметь проверку регулярного выражения и должно выдавать ошибку, если формат недействителен.
Для геттеров, возможно, когда значение равно null, но, скорее всего, это то, что вы захотите обработать в вызывающем коде (в соответствии с рекомендациями по дизайну).
MSDN: перехват и выброс стандартных типов исключений
Это очень сложный вопрос, и ответ зависит от того, как используется ваш объект. Как показывает практика, методы получения и установки свойств, которые являются «поздним связыванием», не должны генерировать исключения, в то время как свойства с исключительно «ранним связыванием» должны генерировать исключения, когда возникает необходимость. Кстати, инструмент анализа кода Microsoft, на мой взгляд, слишком узко определяет использование свойств.
"позднее связывание" означает, что свойства обнаруживаются посредством отражения. Например, атрибут «Serializeable» используется для сериализации / десериализации объекта с помощью его свойств. Вызов исключения во время такого рода ситуации приводит к катастрофическим сбоям в работе и не является хорошим способом использования исключений для создания более надежного кода.
«раннее связывание» означает, что использование свойства ограничено в коде компилятором. Например, когда некоторый код, который вы пишете, ссылается на средство получения свойства. В этом случае можно создавать исключения, когда они имеют смысл.
Состояние объекта с внутренними атрибутами определяется значениями этих атрибутов. Свойства, выражающие атрибуты, которые осведомлены о внутреннем состоянии объекта и чувствительны к нему, не должны использоваться для позднего связывания. Например, допустим, у вас есть объект, который нужно открыть, получить к нему доступ, а затем закрыть. В этом случае доступ к свойствам без предварительного вызова open должен привести к исключению. Предположим в этом случае, что мы не генерируем исключение и разрешаем коду доступ к значению, не генерируя исключения? Код будет казаться счастливым, даже если он получил значение от бессмысленного получателя. Теперь мы поместили код, который вызывал геттер, в плохую ситуацию, поскольку он должен знать, как проверить значение, чтобы убедиться, что оно не имеет смысла. Это означает, что код должен делать предположения о значении, которое он получил от средства получения свойства, чтобы его проверить. Так пишется плохой код.
У меня был этот код, в котором я не был уверен, какое исключение нужно выбросить.
public Person
{
public string Name { get; set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
if (person.Name == null) {
throw new Exception("Name of person is null.");
// I was unsure of which exception to throw here.
}
Console.WriteLine("Name is: " + person.Name);
}
Я запретил модели иметь свойство равным нулю в первую очередь, заставив его в качестве аргумента в конструкторе.
public Person
{
public Person(string name)
{
if (name == null) {
throw new ArgumentNullException(nameof(name));
}
Name = name;
}
public string Name { get; private set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
Console.WriteLine("Name is: " + person.Name);
}