Reputation: 2300
I wanted to write an extension method that would generate and thow generic exception when some condition met. I came up with this code:
internal static TSource ConditionalThrow<TSource, TException>(this TSource source, Func<TSource, bool> throwCondition, TException exception, params object[] arguments) where TException : Exception
{
if (throwCondition(source))
{
throw CreateInstance(exception.GetType(), arguments) as Exception;
}
else
{
return source;
}
}
The problem with this is that user must actually initialize exception
argument himself, so it can be simplified to:
internal static TSource ConditionalThrow<TSource, TException>(this TSource source, Func<TSource, bool> throwCondition, TException exception) where TException : Exception
{
if (throwCondition(source))
{
throw exception;
}
else
{
return source;
}
}
But I would like that extension would generate the error. A thought was to pass Type
instead of TException
, but then I cannot limit it to be Exception
type.
internal static TSource ConditionalThrow<TSource>(this TSource source, Func<TSource, bool> throwCondition, Type exception)
{
if (throwCondition(source))
{
throw CreateInstance(exception, arguments) as Exception;
}
else
{
return source;
}
}
Can I actually achieve that extension would generate and thow generic exception?
I looked into this, but had no success.
Upvotes: 0
Views: 191
Reputation: 27962
It's obvious that specifying both the type of the source and of the exception is better design, allowing for generic type contraints to be applied. Then to remove the necessity to specify both TSource
and TException
explicitly, you can decompose ConditionalThrow
into two methods like this:
internal static void Throw<TException>(this bool condition, params object[] args) where TException : Exception
{
if (condition) throw (Exception)Activator.CreateInstance(typeof(TException), args);
}
internal static bool If<TSource>(this TSource source, Func<TSource,bool> condition)
{
return condition(source);
}
Then the usage would be like this:
10.If(x => x>0).Throw<Exception>();
However, this does not allow passing source
back from Throw
. As the OP correctly suggested, a workaround here is to wrap the call with another method that would ensure source
is returned:
internal static TSource Do<TSource> (this TSource source, Action<TSource> action)
{
action(source);
return source;
}
int a = 10.Do(s => s.If(x => x>0).Throw<Exception>()) + 1;
Another option is to decompose the instatiation of the exception into a conditonally-executed delegate, like this:
internal static TSource ThrowIf<TSource>(this TSource source, Func<TSource,bool> condition, Func<Exception> buildException)
{
if (condition(source))
throw buildException();
else
return source;
}
Then the usage would be like this:
int a = 10.ThrowIf(x => x>0, () => new Exception()) + 1;
While this requires one more () =>
lambda construct, the source
may be returned in case of no-throw, and there's no need for an extra wrapping like in the former case.
A big advantage of the second approach is that both exception instantiation and exception arguments' evaluation is lazy-evaluated, i.e. only in case the exception is really supposed to be thrown. This may be a serious positive memory and performance factor, eliminating side-effects in non-exception code paths.
Upvotes: 1
Reputation: 6219
Yes, like using this:
internal static TSource ConditionalThrow<TSource>(this TSource source, Func<TSource, bool> throwCondition, Type exceptionType, params object[] arguments)
{
if (!typeof(Exception).IsAssignableFrom(exceptionType))
throw new ArgumentException("exceptionType is not an Exception");
if (throwCondition(source))
throw Activator.CreateInstance(exceptionType, arguments) as Exception;
return source;
}
internal static TSource ConditionalThrow<TSource>(this TSource source, Func<TSource, bool> throwCondition, Func<Exception> exeptionBuilder)
{
if (throwCondition(source))
throw exeptionBuilder();
return source;
}
Lets use the sample classes to test our solution
public class Temp
{
public string Name { get; set; }
}
public class MyException : Exception
{
public MyException(string name, string age)
: base($"Name: {name} and Age: {age}")
{ }
public MyException()
: base("No parameter")
{ }
}
try
{
new Temp().ConditionalThrow(t => true, typeof(MyException), "Alberto", "25");
}
catch (MyException ex)
{
Console.WriteLine(ex.Message);
}
try
{
new Temp().ConditionalThrow(t => true, typeof(MyException));
}
catch (MyException ex)
{
Console.WriteLine(ex.Message);
}
try
{
new Temp().ConditionalThrow(t => true, typeof(string));
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
Name: Alberto and Age: 25
No parameter
exceptionType is not an Exception
Working sample: https://dotnetfiddle.net/brIjq9
try
{
new Temp().ConditionalThrow(t => true, () => new MyException("Alberto", "25"));
}
catch (MyException ex)
{
Console.WriteLine(ex.Message);
}
try
{
new Temp().ConditionalThrow(t => true, () => new MyException());
}
catch (MyException ex)
{
Console.WriteLine(ex.Message);
}
Name: Alberto and Age: 25
No parameter
Working sample: https://dotnetfiddle.net/8ZQiIc
Upvotes: 2
Reputation: 22945
You can use the new()
generic constraint so you can create the exception in the method. The downside is that you have to specify the generic parameters, they cannot be inferred.
internal static TSource ConditionalThrow<TSource, TException>(this TSource source, Func<TSource, bool> throwCondition)
where TException : Exception, new()
{
if (throwCondition(source)) {
throw new TException();
} else {
return source;
}
}
Or you can use your current method which creates the exception yourself, but specify the type through the generic argument instead of a Type parameter.
internal static TSource ConditionalThrow<TSource, TException>(this TSource source, Func<TSource, bool> throwCondition)
where TException : Exception
{
if (throwCondition(source)) {
throw CreateInstance(typeof(TException), arguments) as Exception;
} else {
return source;
}
}
Upvotes: 0