FormattedException instead of throw new Exception(string.Format(...)) in .NET
I have been thinking of a good generic exception object that would replace throw new Exception(string.Format("...",...))
, to both simplify and also to speed up such objects. The 开发者_开发百科formatting by slow String.Format()
should be delayed until Message
property is called. Serialization is also somewhat risky. Also, such object could later implement localization.
Update: This exception should be inherited by more specific user exceptions, not thrown itself. Sorry for not making this clear.
This is what I have come up with. Please comment if there are any ways to improve it. Thanks!
/// <summary>
/// Generic exception capable of delayed message formatting.
/// Inherit for more specific exceptions.
/// </summary>
[Serializable]
public class FormattedException : Exception
{
private readonly object[] _arguments;
private readonly string _formatStr;
private readonly bool _useFormat;
private FormattedException(bool useFormat, Exception inner, string message, params object[] args)
: base(message, inner)
{
_useFormat = useFormat;
_formatStr = message;
_arguments = args;
}
public FormattedException()
: this(false, null, null, null)
{}
public FormattedException(string message)
: this(false, null, message, null)
{}
public FormattedException(string message, params object[] args)
: this(true, null, message, args)
{}
public FormattedException(Exception inner, string message)
: this(false, inner, message, null)
{}
public FormattedException(Exception inner, string message, params object[] args)
: this(true, inner, message, args)
{}
public override string Message
{
get
{
if (!_useFormat)
return _formatStr;
try
{
return string.Format(_formatStr, _arguments);
}
catch (Exception ex)
{
var sb = new StringBuilder();
sb.Append("Error formatting exception: ");
sb.Append(ex.Message);
sb.Append("\nFormat string: ");
sb.Append(_formatStr);
if (_arguments != null && _arguments.Length > 0)
{
sb.Append("\nArguments: ");
for (int i = 0; i < _arguments.Length; i++)
{
if (i > 0) sb.Append(", ");
try
{
sb.Append(_arguments[i]);
}
catch (Exception ex2)
{
sb.AppendFormat("(Argument #{0} cannot be shown: {1})", i, ex2.Message);
}
}
}
return sb.ToString();
}
}
}
#region Serialization
private const string SerializationField = "FormatString";
protected FormattedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
_formatStr = (string) info.GetValue(SerializationField, typeof (string));
// Leave other values at their default
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
// To avoid any serialization issues with param objects, format message now
info.AddValue(SerializationField, Message, typeof (string));
}
#endregion
}
It's an interesting thought, but not a good idea. The reason to create a custom exception have nothing to do with ease of use - only create a custom exception if someone is going to catch that exception type and do something different with it.
Instead of a custom exception, maybe you can create an extension method.
This,IMO, isn't a very good design.
Just looking at your first parameter for the FormattedException (useFormat) smells bad. If it's NOT a Formatted Exception (useFormat=false), why am I using a FormattedException? That shows bad design. Which basically leads to this: you're abusing inheritance.
You're using inheritance as a form a UTILS class, or some kind of way to have a lot of common and easy functionality in many classes. This is supposed to help you get a DRY approach, except that I feel it isn't a very OO approach. Does this relationship represent "IS A" relationship? I think it doesn't. I think this represents a "Has a" relationship, which means that the class Has an ability to create a formatted exception, instead of a "Is a" formatted Exception.
Maybe an interface is more appropriate? Maybe a decorator pattern? Maybe something else? Have you thought of other solutions other than inheritance?
IMO what you need is a simple Factory (maybe using Extension methods)... which returns whatever type of exception you give it.
Of course this is just my .02 cents, so I might be completely off.
Oh, BTW, those try/catch inside the Exception are making my eyes bleed.
I have a similar class that I have been using for several years, also named FormattedException. One thing you should do is make the class abstract so that is must be inherited by exception classes that use the extended formatting constructor overloads. I would not worry about the performance implications of using String.Format() internally as once an exception is thrown, a much larger impact to overall application performance will take place and the performance impact of a single call to String.Format() is insignificant. I do agree in your overall approach to simplifying the code used to throw exceptions with a formatted string message and I have often found myself embedding a call to String.Format() for the message parameter of many exceptions that I have thrown.
I threw together a project and NuGet package for my version of FormattedException that I have been using and published it on GitHub and NuGet Gallery.
FormattedException source on GutHub
FormattedException NuGet package
Shouldn't you serialize the arguments as well?
Exception handling strategies are based on exception types not exception messages. If you want user to handle your exception you should add meaningful semantic for it. But I truly don't understand how I can handle your exception (expept to show it for the user).
And I think it not a good idea to compute message inside Message property (multiple access to it may cause significant performance drawback). And all your property should behave like fields (here I talked a bit about it).
精彩评论