条件通用异常抛出的扩展

Extension for conditional generic exception throwing

我想编写一个扩展方法,当满足某些条件时,它会 生成 thow 通用异常。我想出了这个代码:

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

这个问题是用户实际上必须自己初始化 exception 参数,因此可以简化为:

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

但我希望该扩展会产生错误。一个想法是通过 Type 而不是 TException,但是我不能将它限制为 Exception 类型。

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

我真的可以实现扩展 生成 thow 通用异常吗?

我调查了 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;
}

让我们使用示例 类 来测试我们的解决方案

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

工作样本: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

工作样本:https://dotnetfiddle.net/8ZQiIc

您可以使用 new() 通用约束,以便在方法中创建异常。缺点是您必须指定泛型参数,无法推断它们。

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

或者您可以使用自己创建异常的当前方法,但通过通用参数而不是 Type 参数指定类型。

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

很明显,同时指定源类型和异常类型是更好的设计,允许应用通用类型约束。然后为了消除明确指定 TSourceTException 的必要性,您可以将 ConditionalThrow 分解为两种方法,如下所示:

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

那么用法是这样的:

10.If(x => x>0).Throw<Exception>();

但是,此 不允许 Throw 传回 source。正如 OP 正确建议的那样,这里的解决方法是使用另一种方法包装调用,以确保返回 source

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;

另一种选择是将异常的 instatiation 分解为 conditonally-executed 委托,如下所示:

internal static TSource ThrowIf<TSource>(this TSource source, Func<TSource,bool> condition, Func<Exception> buildException)
{
    if (condition(source))
        throw buildException();
    else
        return source;
}

那么用法是这样的:

int a = 10.ThrowIf(x => x>0, () => new Exception()) + 1;

虽然这还需要一个 () => lambda 构造,但在 no-throw 的情况下 source 可能会返回 ,并且没有必要像前一种情况那样进行额外包装。

第二种方法的一大优点是异常实例化和异常参数的评估都是lazy-evaluated,即只有在异常确实应该是的情况下抛出。这 可能 是一个重要的积极内存和性能因素,消除了 non-exception 代码路径中的 side-effects。