静态抛出类:好还是坏的做法?

15

抛出异常通常遵循以下模式:

if(condition) { throw exception; }

您检查条件,如果满足条件,则抛出异常。那么,我想知道是否推荐编写一个静态类来实现这个功能,代码可能如下:

public static class Throw
{
    public static void IfNullOrEmpty<T>(string @string, params object[] parameters) where T : Exception
    {
        Throw.If<T>(string.IsNullOrEmpty(@string), parameters);
    }

    public static void IfNullOrEmpty<T, I>(IEnumerable<I> enumerable, params object[] parameters) where T : Exception
    {
        Throw.If<T>(enumerable == null || enumerable.Count() == 0, parameters);
    }

    public static void IfNullOrEmpty(string @string, string argumentName)
    {
        Throw.IfNullOrEmpty(@string, argumentName, 
            string.Format("Argument '{0}' cannot be null or empty.", argumentName));
    }

    public static void IfNullOrEmpty(string @string, string argumentName, string message)
    {
        Throw.IfNullOrEmpty<ArgumentNullOrEmptyException>(@string, message, argumentName);
    }

    public static void IfNullOrEmpty<I>(IEnumerable<I> enumerable, string argumentName)
    {
        Throw.IfNullOrEmpty(enumerable, argumentName, 
            string.Format("Argument '{0}' cannot be null or empty.", argumentName));
    }

    public static void IfNullOrEmpty<I>(IEnumerable<I> enumerable, string argumentName, string message)
    {
        Throw.IfNullOrEmpty<ArgumentNullOrEmptyException, I>(enumerable, message, argumentName);
    }


    public static void IfNull<T>(object @object, params object[] parameters) where T : Exception
    {
        Throw.If<T>(@object == null, parameters);
    }

    public static void If<T>(bool condition, params object[] parameters) where T : Exception
    {
        if (condition) 
        {
            var types = new List<Type>();
            var args = new List<object>();
            foreach (object p in parameters ?? Enumerable.Empty<object>())
            {
                types.Add(p.GetType());
                args.Add(p);
            }

            var constructor = typeof(T).GetConstructor(types.ToArray());
            var exception = constructor.Invoke(args.ToArray()) as T;
            throw exception;
        }
    }

    public static void IfNull(object @object, string argumentName)
    {
        Throw.IfNull<ArgumentNullException>(@object, argumentName);
    }
}

(注:这里未定义ArgumentNullOrEmptyException,但它基本上做了人们所期望的事情。)
因此,不要反复编写这样的内容。
void SomeFunction(string someParameter)
{
   if(string.IsNullOrEmpty(someParameter))
   {
      throw new ArgumentNullOrEmptyException("someParameter", "Argument 'someParameter' cannot be null or empty.");
   }
}

我只是做

void SomeFunction(string someParameter)
{
   Throw.IfNullOrEmpty(someParameter, "someParameter"); // not .IsNullOrEmpty
}

我其实喜欢它,但这是否也是一个好的做法呢?

我认为这是一个相当常见的帮助/实用类,在大多数项目中都存在。 - sll
3
负面影响是它会在堆栈跟踪中添加一些无用信息(除非您在Throw类中添加代码以将其对堆栈跟踪的贡献删除)。 - hatchet - done with SOverflow
5
我之前做过类似的事情,但我称其为“Guard”,并使用了[DebuggerStepThrough]属性。 - Brian
@Brian - 对你的评论点赞。非常好的补充。 - John Buchanan
10个C#开发者应该知道的实用工具解释了一个名为Throw类的价值,类似于您编写的类。 - Steven Wexler
3个回答

12
这种方法可以消除代码重复(if ... throw),从这个意义上说,这是一个好主意。只需注意,要能够阅读和理解代码,人们需要了解Throw API。
一种改进方法是使用表达式树来消除字符串参数名称传递。这将进一步提高简单性,并且您不必担心在重构期间输入字符串并保持正确。
例如,在我的当前宠物项目中,我有这个Guard类(稍微缩短了一点):
public static class Guard
{
    public static void NotNullOrEmpty(Expression<Func<string>> parameterExpression)
    {
        string value = parameterExpression.Compile()();
        if (String.IsNullOrWhiteSpace(value))
        {
            string name = GetParameterName(parameterExpression);
            throw new ArgumentException("Cannot be null or empty", name);
        }
    }

    public static void NotNull<T>(Expression<Func<T>> parameterExpression)
        where T : class
    {
        if (null == parameterExpression.Compile()())
        {
            string name = GetParameterName(parameterExpression);
            throw new ArgumentNullException(name);
        }
    }

    private static string GetParameterName<T>(Expression<Func<T>> parameterExpression)
    {
        dynamic body = parameterExpression.Body;
        return body.Member.Name;
    }
}

我可以这样使用它:

然后我可以像这样使用:

Guard.NotNull(() => someParameter);

2
在这种方式中使用表达式树 -- 比如在每个方法的顶部添加一两个或三个保护 -- 是否会增加很多开销?(对于 MSIL + 运行时来说?)通常我不关心这些事情,但它似乎可以轻松地隐藏复杂性... - user166390
对于大多数情况,开销很小。在紧密循环中,请考虑不使用此方法。 - driis

7

这种模式没有问题,我在许多应用程序中都看到过。这主要是个人风格的问题。

不过,需要注意的是,此模式会改变资源字符串的性能语义。对于具有本地化错误消息的应用程序/库,此模式如下:

if (...) {
  throw new ArgumentExecption("paramName", LoadSomeResource(ErrorId));
}

虽然加载资源并非免费,但也不便宜。在上述模式中,当出现错误时,资源会按需加载。而在您的模式中,它将被急切地加载。这意味着应用程序中的每个资源字符串都将被急切地加载,即使从未违反方法合同。这很可能不是您期望做的事情。


6

1
@pst - 你可以使用 Microsoft.Contracts.dll,它是3.5版本的。 - Otávio Décio
我之前了解过代码契约:它真的是推荐的方式吗?我曾经尝试过使用它,但我记得它在我身上失败得很惨。 - esskar
它对我的目的很有效,还会批评我的实现,找到需要添加检查的地方。我看到的好处是它可以进行微调,对性能影响很小。 - Otávio Décio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接