The Problem
I had been working on a base class for reading and writing to a database. I knew that when a new object (and hence record) was created there needed to be a mechanism for setting default values. I also knew that even with the default values there might not be enough information that the object could be saved.
For example: In a contact database, a new contact would by default have a blank name and blank phone number. Trying to save such a contact should not succeed and instead notify the user about the failure. Too many applications and web pages do this one failure at a time. I wanted to be able to cover all of the failures in one step.
This presents an interesting problem in any user interface. How can an object notify the environment about multiple failures? The standard way to notify the environment about any failure is to throw an exception. But I wanted to throw multiple exceptions and have them all be handled at one time. So was born the idea of a MultiException
object.
Solution
The MultiException
object should itself act as an Exception, but it should also act as a Collection. This way, when a save occurs, the data class has the ability to add exceptions to the MultiException
and then throw the MultiException
. The application can then catch the MultiException
and enumerate all of the Exceptions displaying the reason for each one to the end user.
Implementation
The MultiException
class turned out to be more of a chore then I originally thought. The easiest way to create a class that acts as a collection is to base it on the CollectionBase
class. The problem with this approach was that I wanted MultiException
to be based on ApplicationException
and .Net only allows us one base class. .Net does, however, allow for a class to be based on one class and multiple interfaces.
MultiException
was created based on ApplicationException
but also implements IList
. Being based on ApplicationException
means that MultiException
can be thrown as an exception. Implementing IList
means that new exceptions can be added, gives us the ability to use indexes ( the [ ] operator ), and even allows us to use foreach
loops!
Basing the class on ApplicationException
was the easy part. Implementing IList
was a bit more vague. It's not that implementing an interface is difficult, it's just that I didn't want to expose the IList
interface. Why not, you ask? Because the IList
interface is far too generic.
The IList
interface includes methods like:
public int Add(object O)
and
public object this[int index]
Obviously we want to add Exceptions and Exceptions only, but the Add
method of the IList
interface accepts any old object. Sure we could test the object as it's passed in and throw an ArgumentException, but it just doesn't look nice and moreover doesn't enforce type-safety.
The obvious answer is to type our Add method correctly:
public int Add(Exception E)
But the compiler will balk at you twice for this move! It will tell you first that the Add
method of IList
isn't implemented and second that the closest match doesn't have the right arguments.
What about making the generic version private? Nope, no good there. The compiler won't accept it as private either which is rightly so. How would anything access the method if we cast our object as an IList
?
So then how do we do this right? It actually took me quite a while to find this one (it's easy to overlook). The trick is that you prefix the name of the interface to the generic version, and you make your type-safe version public. Example:
int IList.Add(object O)
public int Add(Exception E)
Notice how you don't even mark the prefixed version public or private. C# understands that the prefixed version is there only to suffice the interface which makes this method operate in a special way. The prefixed version is used while the object is cast as an IList
, but isn't even available while the object is a standard MultiException
.
Conclusion
I use MultiException
any time I have more then one error to report, and hiding implementation members is a hard-to-find trick. I hope you'll be able to use both in your future projects.