debugging 为什么.net在引发KeyNotFound异常时不提供密钥(我如何获得它?))

r6l8ljro  于 2023-06-30  发布在  .NET
关注(0)|答案(5)|浏览(114)

当你试图访问一个不在字典中的键时(例如),这是你得到的堆栈跟踪:

at System.ThrowHelper.ThrowKeyNotFoundException()
     at System.Collections.Generic.Dictionary`2.get_Item(TKey key)   
       .... .... (my own code stack trace)

就像大多数人可能做的那样,我在错误发生时记录这些错误,并试图找出发生了什么。
我想要的两个关键信息是这是在哪里发生的(堆栈跟踪对此非常有帮助),以及导致异常的密钥,它没有出现在任何地方。
要么是我没有正确查看(KeyNotFoundException类包含一个“Data”成员,它总是空的,并且“Message”字段“不包含键值”),要么是它根本没有包含在框架中。
我无法想象.net BCL团队中没有人认为这是一个有用的功能。我很好奇他们为什么不包括它。有什么好的理由不这样做吗?
如何处理这些例外情况?我能想到的唯一替代方案是在Dictionary上使用自定义扩展方法,该方法将 Package 调用,捕获异常,并使用有关键的附加信息重新抛出它,但这对我不拥有的代码没有帮助,并且改变这样的“基础”功能感觉很糟糕。
你觉得呢?
我知道this question,关于一个无法访问引发异常的方法的参数的一般事实。我的问题与KeyNotFoundException特别相关。
编辑:我知道TryGetValue模式,当我期望我的集合可能不包含键时,我总是这样做。但是当它应该包含它时,我不测试,因为我更喜欢我的程序失败并出现异常,这样我就知道在 * 之前 * 发生了一些意想不到的事情(即在应该插入密钥的时候)。
我可以用try/catch/log error/rethrow来 Package 所有的字典访问,但这会导致代码难以阅读,并与所有异常处理/日志记录的东西混在一起。

twh00eeo

twh00eeo1#

为什么要依赖(慢)异常?只需验证密钥是否存在ContainsKeyTryGetValue
我不知道为什么异常不包含导致错误的字段(也许是因为它应该是非泛型的),但是如果你认为你需要它,就把它 Package 起来。

class ParameterizedKeyNotFoundException<T> : KeyNotFoundException {
    public T InvalidKey { get; private set; }

    public ParameterizedKeyNotFoundException(T InvalidKey) {
        this.InvalidKey = InvalidKey;
    }
}

static class Program {

    static TValue Get<TKey, TValue>(this IDictionary<TKey, TValue> Dict, TKey Key) {
        TValue res;

        if (Dict.TryGetValue(Key, out res))
            return res;

        throw new ParameterizedKeyNotFoundException<TKey>(Key);
    }

    static void Main(string[] args) {

        var x = new Dictionary<string, int>();

        x.Add("foo", 42);

        try {
            Console.WriteLine(x.Get("foo"));
            Console.WriteLine(x.Get("bar"));
        }
        catch (ParameterizedKeyNotFoundException<string> e) {
            Console.WriteLine("Invalid key: {0}", e.InvalidKey);
        }

        Console.ReadKey();
    }
}
dgiusagp

dgiusagp2#

您可以使用ContainsKey或TryGetValue方法来验证字典是否包含该键。

dfty9e19

dfty9e193#

正如darin指出的,你应该在你的代码中考虑到这一点,但是如果出于某种原因,这是不可能的,你可以做下面的事情。

public object GetObjectFromDictionary(string key)
{
    try
    {
        return MyDictionary[key];
    }
    catch (KeyNotFoundException kex)
    {
        throw new WrappedException("Failed To Find Key: " + key, kex);
    }
}
j91ykkif

j91ykkif4#

这类信息可能不会添加到堆栈跟踪中,因此意外显示给用户或添加到用户日志中的异常不包含有关应用程序的内部信息。任何包含运行时数据的异常都可能被攻击者利用。
话虽如此,我认为不使用TryXXX方法提供程序是愚蠢的。当该方法返回false表示键不存在时,您可以跟踪丢失的键,将其写入日志,在调试、Assert等模式下运行时显示弹出窗口。所以你知道丢失的钥匙。一般来说,出于我上面提到的原因,不要向用户显示它。

2exbekwf

2exbekwf5#

我真的不能让你明白为什么没有在异常中包含键。正如达林所说,你应该总是使用TryGetValue -它使代码更容易阅读,维护,你不会得到KeyNotFoundException(并且,当你在它,总是使用TryParse而不是Parse,对于int,double,DateTime,无论什么,出于同样的原因)。
但是,一旦你得到了它们:
如何处理这些例外情况?
既然你知道异常是在哪里抛出的,如果代码不是非常复杂,这就允许你重现bug(启用“抛出异常时中断”,或者在英语版本中的任何名称,在调试->异常对话框中,这个异常在那一刻中断),我通常会在几分钟内找到原因。调试器会告诉你在这种情况下的键是什么,然后你的工作就是找出为什么这个键是键(或者为什么这个键不在字典中),即使你是通过bug报告得到这个键的名字的。除非你有某种上帝字典,绝对一切都在,它应该是相当容易找出问题在哪里。
代码中不应该有任何未处理的异常,所以遇到这种情况应该是罕见的,然后我可以进一步调查。换句话说,我从来没有觉得有必要从bug报告中获取密钥。
当然,如果你不能重现bug,这对你没有帮助--然后我通常会在失败的区域添加日志代码,并将该版本交给遇到bug的客户(如果不止一个客户,通常可以用客户的数据重现bug)。
但是,我正在为其他公司开发软件,而不是收缩 Package 软件,所以关于你可以花在客户身上的时间,事情可能会有所不同。

相关问题