C# 重新引发已包装对象的异常

C# 重新引发已包装对象的异常,c#,.net,exception,exception-handling,try-catch,C#,.net,Exception,Exception Handling,Try Catch,我使用ConcurrentDictionary实现ConcurrentSet 或者我应该 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 (ArgumentNullExc

我使用
ConcurrentDictionary
实现
ConcurrentSet

或者我应该

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;
}
或者我应该

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;
}
或者我应该

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

正确的方法是什么?

在您的示例中,它们非常相似,但我会选择第一个选项:

if (item == null)
    throw new ArgumentNullException("item", "Item must not be null.");
因为它不需要
捕捉
,而且看起来更紧凑。如果需求发生变化,并且不会增加更多的代码行,它还可以让您在条件上进行扩展,例如

if (item==null || item.Name == null)
    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);
这取决于类是否关心是否存在空值


如果执行空值检查的唯一原因是为了避免将空值传递给
TryAdd
,那么不要费心检查
TryAdd
将执行自己的检查并引发异常

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

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

只有当您打算对异常进行处理时,才应该捕获它。如果您只是想重新抛出,或者创建一个新的等价表达式,那么就不要费心去捕捉它。

我想说的是,您应该做什么取决于您想要的效果。是否要接受错误而不向用户显示?不要在catch框中重新抛出错误,但一定要包含try catch。要自定义错误消息,请执行
项==null
检查。生成一个新的异常实例并没有多大用处,所以无论如何这都是不可能的

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


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

您应该做什么取决于您想要记录类的操作。如果您希望记录添加空项的尝试可能会以未指定的方式失败,那么只需直接进行调用,并允许出现任何异常。如果您希望记录您将返回
参数名
等于
ArgumentNullException
,并且不希望依赖
ConcurrentDictionary
在接收空键时的行为,那么您应该在将参数传递到
ConcurrentDictionary
之前检查参数。如果您希望记录代码将抛出
参数名等于
项的
ArgumentNullException
,但愿意依赖
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;
}
该代码避免了在参数不为null的情况下(在99.9999%的情况下)进行参数验证的任何额外成本,但仍将确保在因预期原因发生异常的情况下,该代码仅声明为
ArgumentNullException
的源;如果
ConcurrentDictionary
中的错误导致它意外地将null参数传递给它内部调用的方法,即使给它添加了非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
ConcurrentDictionary.Add
中转义,而不是因为
为空,这样一个异常不应该被那些可能期望您发出
ArgumentNullException
的代码捕获。

请查看关于重新抛出异常的答案:您能补充一下关于用
ArgumentNullException
包装
ArgumentNullException
的意义吗?@Ilyavanov:我认为这个想法是这样的由于基本问题是使用空值调用了
ConcurrentSet.Add
,这就是堆栈跟踪应该显示的内容。此外,抛出的异常应该表明null参数的名称是“item”而不是“key”。@IlyaIvanov:就像supercat所说的,由
ConcurrentDictionary
抛出的
ArgumentNullException
将其
ParamName
属性设置为“key”,而不是“item”。这不是此类的预期行为,它可能会混淆
ConcurrentSet
的使用者,由于它通过异常的
ParamName
属性和源自
ConcurrentDictionary
@wonko79的堆栈跟踪公开了实现细节:这不是您提到的问题的重复,因为实际上这更多的是关于API设计而不是错误处理。ConcurrentSet的使用者应该处理ArgumentNullException,如果他们将null值传递给Add(T)方法,但是他们应该知道它是由并发字典抛出的,还是堆栈跟踪应该引导他们添加(T)而不是TryAdd(TKey,TValue)?哪一个更糟糕,验证一个将由包装类验证的参数,还是让第二个异常被初始化?+1来自我。我自己会选择这两种方法中的第二种——因为堆栈跟踪将在更接近错误点的地方开始