重新抛出包装对象的异常

本文关键字:异常 对象 包装 新抛出 | 更新日期: 2023-09-27 18:09:36

我使用ConcurrentDictionary<TKey, TValue>来实现ConcurrentSet<T>

public class ConcurrentSet<T> : ISet<T>
{
    private readonly ConcurrentDictionary<T, byte> collection;
}

ConcurrentDictionary<TKey, TValue>不能包含null键的pair

// summary, param, returns...
/// <exception cref="ArgumentNullException">
///     <paramref name="item" /> is null.
/// </exception>
public bool Add(T item)
{
    // This throws an argument null exception if item is null.
    return this.collection.TryAdd(item, 0);
}

所以,我应该,

if (item == null)
    throw new ArgumentNullException("item", "Item must not be null.");
return this.collection.TryAdd(item, 0);

或者,我应该

try
{
    return this.collection.TryAdd(item, 0);
}
catch (ArgumentNullException)
{
    throw new ArgumentNullException("item", "Item must not be null.");
}

或者,我应该

try
{
    return this.collection.TryAdd(item, 0);
}
catch (ArgumentNullException x)
{
    // I know, but I don't want to preserve the stack trace
    // back to the underlying dictionary, anyway.
    throw x;
}

或者,我应该

try
{
    return this.collection.TryAdd(item, 0);
}
catch (ArgumentNullException)
{
    // The thrown exception will have "key", instead of
    // "item" as the parameter's name, in this instance.
    throw;
}

正确的方法是什么?

重新抛出包装对象的异常

我会选择这个

public bool Add(T item)
{
    // This throws an argument null exception if item is null.
    return this.collection.TryAdd(item, 0);
}

if (item == null)
    throw new ArgumentNullException("item", "Item must not be null.");
return this.collection.TryAdd(item, 0);

这取决于你的类是否关心是否存在null。

如果执行null检查的唯一原因是避免将null传递给TryAdd,那么就不要检查了。TryAdd会自己检查并抛出异常。

如果在某些时候你认为你可能会使用一个允许null的不同集合,但你仍然希望你的集合不包含null,那么你应该自己检查一下。这将保护您在将来的某个时间点发生更改。

参数验证应该总是方法要做的第一件事。如果参数无效,那么做其他任何事情都没有意义。

只有当你要处理异常时,你才应该捕捉异常。如果你只是想重新抛出,或者创建一个等价的新表达式,那么就不要捕获它了。

你应该做什么取决于你想记录你的类做什么。如果您希望记录添加空项的尝试可能以未指定的方式失败,那么只需直接进行调用,并让任何异常冒出。如果您希望记录您将返回ArgumentNullException,其中ParamName等于item,并且不希望依赖于ConcurrentDictionary在接收空键时的行为,那么您应该在将其传递给ConcurrentDictionary之前检查参数。如果您希望记录您的代码将抛出ArgumentNullException,其中ParamName等于item,但愿意依赖ConcurrentDictionary来验证其参数并抛出ArgumentException,如果性能至关重要,另一种可能性是:

try
{
    return this.collection.TryAdd(item, 0);
}
catch (ArgumentNullException ex)
{
    if (ex.ParamName == "key" && item == null)
        throw new ArgumentNullException("item", "Item must not be null.");
    else
        throw;
}

这段代码避免了在参数不为空的情况下(99.9999%的情况应该是这样)参数验证的任何额外成本,但仍然确保它只会在由于预期原因发生这种异常的情况下声称是ArgumentNullException的来源;如果ConcurrentDictionary中的错误导致它意外地将一个null参数传递给它内部调用的方法,即使它被赋予一个非空项来添加,上述代码将确保原始异常堆栈跟踪不会丢失。注意另一种可能是:

    if (ex.ParamName == "key" && item == null)
        throw new ArgumentNullException("item", "Item must not be null.");
    else
        throw new UnexpectedException(ex); // Probably a custom type

基本思想是,如果ArgumentNullException由于item为null以外的原因从ConcurrentDictionary.Add中转义,那么这种异常应该被可能期望从您那里获得ArgumentNullException的代码捕获。

我想说的是,你应该做什么取决于你想要的效果。您想吞下错误而不向用户显示它吗?不要在catch框中重新抛出错误,但要包含try-catch。如果您想要自定义错误消息,请使用Item == null检查。生成一个新的异常实例并没有多大用处,所以不管怎样,这都是不可能的。

如果你没有记录错误,或者在上游处理它,那么在捕获错误后就没有必要重新抛出错误。否则,这取决于个人风格以及您是否需要自定义错误消息。

我最喜欢的可能是Item == null检查自定义错误消息,但那是因为我喜欢自定义错误消息。我发现它们对我更有用,但要确保在调用这个方法的东西周围有错误处理,这样错误就不会导致未处理的异常。

在你的例子中,它们都太相似了,但我还是选择第一个选项:

if (item == null)
    throw new ArgumentNullException("item", "Item must not be null.");

,因为它不需要catch,看起来更紧凑。如果需求发生变化,它还可以让您扩展条件,而不会添加更多的代码行,例如

if (item==null || item.Name == null)
    throw...