Dovydas Šopa
Dovydas Šopa

Reputation: 2300

Extension for conditional generic exception throwing

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

Answers (3)

Ondrej Tucny
Ondrej Tucny

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

Alberto Monteiro
Alberto Monteiro

Reputation: 6219

Yes, like using this:

First option (no compiler time validation for exeception type)

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;
}

Second option (with compiler time validation for exeception type)

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")
    { }
}

Testing the first option

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);
}

Output for first option

Name: Alberto and Age: 25

No parameter

exceptionType is not an Exception

Working sample: https://dotnetfiddle.net/brIjq9

Testing the second option

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);
}

Output for second option

Name: Alberto and Age: 25

No parameter

Working sample: https://dotnetfiddle.net/8ZQiIc

Upvotes: 2

Maarten
Maarten

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

Related Questions