Everytime I need to provide additional information about an exception I wonder which way is actually the right way of doing this.
For the sake of this question I wrote an example. Let's assume there is a class where we want to update the Abbreviation
property. From the SOLID point of view it might not be perfect but even if we passed the worker-method via DI with some service the same situation would occur - an exception occurs an there is no context to it. Back to the example...
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
Then there are some instances of the class and a loop where the worker-method is called. It can throw the StringTooShortException
.
var persons =
{
new Person { Id = 1, Name = "Fo" },
new Person { Id = 2, Name = "Barbaz" },
}
public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
foreach (var person in persons)
{
try
{
person.Abbreviation = GenerateAbbreviation(person.Name);
}
catch(Exception ex)
{
// ?
}
}
// throw AggregateException...
}
public IEnumerable<string> GenerateAbbreviation(string value)
{
if (value.Length < 5)
{
throw new StringTooShortException(value);
}
// generate abbreviation
}
The quesion is: how to add the Person
or its Id
(or anything else)?
I know the following three techniques:
1 - Use the Data
property
Pros:
- easy to set additional information
- does not require creating even more exceptions
- does not require additional
try/catch
Cons:
- cannot be easily integrated into the
Message
- loggers ignore this field and won't dump it
- requires keys and casting becasue values are
object
- not immutable
Example:
public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
foreach (var person in persons)
{
try
{
person.Abbreviation = GenerateAbbreviation(person.Name);
}
catch(Exception ex)
{
ex.Data["PersonId"] = person.Id;
// collect ex
}
}
// throw AggregateException...
}
2 - Use custom properties
Pros:
- similar to the
Data
property but strongly typed - easier to integrate into the
Message
Cons:
- requires custom exceptions
- logger will ignore them
- not immutable
Example:
public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
foreach (var person in persons)
{
try
{
person.Abbreviation = GenerateAbbreviation(person.Name);
}
catch(Exception ex)
{
// not suitable for this exception because
// it doesn't have anything in common with the Person
}
}
// throw AggregateException...
}
3 - Wrap the exception with another exception
Pros:
Message
can be formatted in a predictable way- loggers will dump inner exceptions
- immutable
Cons:
- requires additional
try/catch
- increses nesting
- increaes the depth of the exeptions
Example:
public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
foreach (var person in persons)
{
try
{
try
{
person.Abbreviation = GenerateAbbreviation(person.Name);
}
catch(Exception ex)
{
throw new InvalidPersonDataException(person.Id, ex);
}
}
catch(Exception ex)
{
// collect ex
}
}
// throw AggregateException...
}
- Are there any other patterns?
- Are there better patterns?
- Can you suggest best practices for any/all of them?