Quando é apropriado lançar uma exceção de dentro de um getter ou setter de propriedade? Quando não é apropriado? Por quê? Links para documentos externos sobre o assunto seriam úteis ... O Google descobriu surpreendentemente poucos.
Quando é apropriado lançar uma exceção de dentro de um getter ou setter de propriedade? Quando não é apropriado? Por quê? Links para documentos externos sobre o assunto seriam úteis ... O Google descobriu surpreendentemente poucos.
Respostas:
A Microsoft tem suas recomendações sobre como projetar propriedades em http://msdn.microsoft.com/en-us/library/ms229006.aspx
Essencialmente, eles recomendam que os getters de propriedade sejam acessadores leves que sempre são seguros para chamadas. Eles recomendam redesenhar getters para serem métodos se as exceções forem algo que você precisa lançar. Para setters, eles indicam que as exceções são uma estratégia de tratamento de erros apropriada e aceitável.
Para indexadores, a Microsoft indica que é aceitável para getters e setters lançar exceções. E, de fato, muitos indexadores na biblioteca .NET fazem isso. A exceção mais comum é ArgumentOutOfRangeException
.
Existem alguns bons motivos pelos quais você não deseja lançar exceções em getters de propriedade:
obj.PropA.AnotherProp.YetAnother
- com esse tipo de sintaxe, torna-se problemático decidir onde injetar instruções catch de exceção.Como observação, deve-se estar ciente de que só porque uma propriedade não foi projetada para lançar uma exceção, isso não significa que não o fará; poderia facilmente chamar um código que o faz. Até o simples ato de alocar um novo objeto (como uma string) pode resultar em exceções. Você deve sempre escrever seu código defensivamente e esperar exceções de qualquer coisa que invocar.
Não há nada de errado em lançar exceções de setters. Afinal, qual a melhor forma de indicar que o valor não é válido para uma determinada propriedade?
Para getters, é geralmente desaprovado e isso pode ser explicado com bastante facilidade: um getter de propriedade, em geral, relata o estado atual de um objeto; portanto, o único caso em que é razoável para um getter lançar é quando o estado é inválido. Mas também é geralmente considerado uma boa ideia projetar suas classes de forma que simplesmente não seja possível obter um objeto inválido inicialmente, ou colocá-lo em um estado inválido por meios normais (ou seja, sempre garantir a inicialização completa nos construtores, e tente tornar os métodos seguros para exceções no que diz respeito à validade do estado e invariantes de classe). Contanto que você siga essa regra, os getters de propriedade nunca devem entrar em uma situação em que tenham que relatar um estado inválido e, portanto, nunca jogue.
Há uma exceção que eu conheço, e na verdade é bastante importante: qualquer implementação de objeto IDisposable
. Dispose
destina-se especificamente a ser uma forma de colocar o objeto em um estado inválido e há até mesmo uma classe de exceção especial,, ObjectDisposedException
para ser usada nesse caso. É perfeitamente normal lançar ObjectDisposedException
de qualquer membro da classe, incluindo getters de propriedade (e excluindo a Dispose
si mesmo), após o objeto ter sido descartado.
IDisposable
devem se tornar inúteis após um Dispose
. Se invocar um membro exigiria o uso de um recurso que Dispose
se tornou indisponível (por exemplo, o membro leria dados de um fluxo que foi fechado), o membro deve lançar ObjectDisposedException
ao invés de vazar ArgumentException
, por exemplo , mas se um tiver um formulário com propriedades que representam o valores em certos campos, seria muito mais útil permitir que tais propriedades sejam lidas após o descarte (produzindo os últimos valores digitados) do que exigir ...
Dispose
seja adiado até depois que todas essas propriedades forem lidas. Em alguns casos em que um thread pode usar o bloqueio de leituras em um objeto enquanto outro o fecha, e onde os dados podem chegar a qualquer momento anterior Dispose
, pode ser útil Dispose
cortar os dados recebidos, mas permitir que os dados recebidos anteriormente sejam lidos. Não se deve forçar uma distinção artificial entre Close
e Dispose
em situações em que, de outra forma, nada precisaria existir.
Get...
método. Uma exceção aqui é quando você precisa implementar uma interface existente que requer que você forneça uma propriedade.
Quase nunca é apropriado em um getter e às vezes apropriado em um setter.
O melhor recurso para esse tipo de pergunta é "Framework Design Guidelines", de Cwalina e Abrams; está disponível como um livro encadernado e grandes partes dele também estão disponíveis online.
Da seção 5.2: Projeto de propriedade
EVITE lançar exceções de getters de propriedade. Os getters de propriedade devem ser operações simples e não devem ter pré-condições. Se um getter pode lançar uma exceção, provavelmente deve ser reprojetado para ser um método. Observe que esta regra não se aplica a indexadores, onde esperamos exceções como resultado da validação dos argumentos.
Observe que esta diretriz se aplica apenas a getters de propriedade. É normal lançar uma exceção em um configurador de propriedade.
ObjectDisposedException
uma vez que o objeto foi Dispose()
chamado e algo subsequentemente pede um valor de propriedade? Parece que a orientação deve ser "evite lançar exceções de getters de propriedade, a menos que o objeto tenha sido descartado, caso em que você deve considerar lançar um ObjectDisposedExcpetion".
Uma boa abordagem para as exceções é usá-las para documentar o código para você e outros desenvolvedores da seguinte maneira:
As exceções devem ser para estados de programa excepcionais. Isso significa que você pode escrevê-los onde quiser!
Um motivo pelo qual você pode querer colocá-los em getters é documentar a API de uma classe - se o software lançar uma exceção assim que um programador tentar usá-lo errado, ele não usará errado! Por exemplo, se você tiver validação durante um processo de leitura de dados, pode não fazer sentido poder continuar e acessar os resultados do processo se houver erros fatais nos dados. Nesse caso, você pode querer fazer a obtenção da saída lançar se houver erros para garantir que outro programador verifique essa condição.
Eles são uma forma de documentar as premissas e limites de um subsistema / método / qualquer coisa. No caso geral, eles não devem ser capturados! Isso também ocorre porque eles nunca são lançados se o sistema estiver trabalhando em conjunto da maneira esperada: se uma exceção acontecer, isso mostra que as suposições de um trecho de código não são atendidas - por exemplo, ele não está interagindo com o mundo ao seu redor da maneira foi originalmente planejado para. Se você detectar uma exceção que foi escrita para este propósito, provavelmente significa que o sistema entrou em um estado imprevisível / inconsistente - isso pode levar a uma falha ou corrupção de dados ou algo semelhante, o que provavelmente será muito mais difícil de detectar / depurar.
Mensagens de exceção são uma forma muito grosseira de relatar erros - elas não podem ser coletadas em massa e contêm apenas uma string. Isso os torna inadequados para relatar problemas nos dados de entrada. Na execução normal, o próprio sistema não deve entrar em estado de erro. Como resultado, as mensagens neles devem ser projetadas para programadores e não para usuários - coisas que estão erradas nos dados de entrada podem ser descobertas e retransmitidas para os usuários em formatos mais adequados (personalizados).
A exceção (haha!) Para esta regra são coisas como IO, onde as exceções não estão sob seu controle e não podem ser verificadas com antecedência.
Tudo isso está documentado no MSDN (conforme vinculado a outras respostas), mas aqui está uma regra geral ...
No setter, se sua propriedade deve ser validada acima e além do tipo. Por exemplo, uma propriedade chamada PhoneNumber provavelmente deve ter validação regex e deve gerar um erro se o formato não for válido.
Para getters, possivelmente quando o valor for nulo, mas provavelmente isso é algo que você desejará tratar no código de chamada (de acordo com as diretrizes de design).
MSDN: Captura e lançamento de tipos de exceção padrão
Esta é uma pergunta muito complexa e a resposta depende de como seu objeto é usado. Como regra geral, getters e setters de propriedade que são "vinculação tardia" não devem lançar exceções, enquanto propriedades com "vinculação antecipada" exclusivamente devem lançar exceções quando necessário. A propósito, a ferramenta de análise de código da Microsoft está definindo o uso de propriedades de maneira muito restrita na minha opinião.
"ligação tardia" significa que as propriedades são encontradas por meio de reflexão. Por exemplo, o atributo Serializeable "é usado para serializar / desserializar um objeto por meio de suas propriedades. Lançar uma exceção durante esse tipo de situação quebra as coisas de uma forma catastrófica e não é uma boa maneira de usar exceções para criar um código mais robusto.
"vinculação antecipada" significa que o uso de uma propriedade é vinculado ao código pelo compilador. Por exemplo, quando algum código que você escreve faz referência a um getter de propriedade. Nesse caso, não há problema em lançar exceções quando elas fizerem sentido.
Um objeto com atributos internos tem um estado determinado pelos valores desses atributos. Propriedades que expressam atributos que estão cientes e sensíveis ao estado interno do objeto não devem ser usadas para vinculação tardia. Por exemplo, digamos que você tenha um objeto que deve ser aberto, acessado e fechado. Nesse caso, acessar as propriedades sem chamar open primeiro deve resultar em uma exceção. Suponha, neste caso, que não lançamos uma exceção e permitimos o acesso do código a um valor sem lançar uma exceção. O código parecerá feliz, embora tenha obtido um valor de um getter que não faz sentido. Agora colocamos o código que chamou o getter em uma situação ruim, pois ele deve saber como verificar o valor para ver se ele não faz sentido. Isso significa que o código deve fazer suposições sobre o valor que obteve do getter da propriedade para validá-lo. É assim que um código ruim é escrito.
Eu tinha esse código em que não tinha certeza de qual exceção lançar.
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);
}
Impedi que o modelo tivesse a propriedade nula em primeiro lugar, forçando-o como um argumento no construtor.
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);
}