Я хотел написать метод расширения, который будет генерировать и 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;
    }
}

Могу ли я добиться того, чтобы расширение генерировало и как общее исключение?

Я просмотрел this, но безуспешно.

0
Dovydas Šopa 29 Дек 2015 в 13:27

3 ответа

Лучший ответ

Очевидно, что указание как типа источника, так и исключения - лучший дизайн, позволяющий применять ограничения общего типа. Затем, чтобы избавиться от необходимости явно указывать как TSource, так и TException, вы можете разложить 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>();

Однако это не позволяет передать source обратно от Throw. Как правильно предложил 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;

Другой вариант - разложить создание исключения на условно выполняемый делегат, например:

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;

Хотя для этого требуется еще одна лямбда-конструкция () =>, source может быть возвращено в случае отсутствия выброса, и нет необходимости в дополнительной упаковке, как в первом случае.

Большим преимуществом второго подхода является то, что как создание экземпляра исключения, так и оценка аргументов исключения выполняется лениво , то есть только в том случае, если действительно предполагается, что исключение должно быть создано. Это может быть серьезным положительным фактором памяти и производительности, устраняя побочные эффекты в путях кода без исключений.

1
Ondrej Tucny 29 Дек 2015 в 14:25

Вы можете использовать универсальное ограничение 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;
    }
}
0
Maarten 29 Дек 2015 в 11:35

Да, вот так:

Первый вариант (без проверки времени компиляции для типа исключения)

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

Выход для первого варианта

Имя: Альберто, возраст: 25

Без параметра

exceptionType не является исключением

Рабочий образец: 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);
}

Выход для второго варианта

Имя: Альберто, возраст: 25

Без параметра

Рабочий образец: https://dotnetfiddle.net/8ZQiIc

2
Community 20 Июн 2020 в 09:12