【问题标题】:When KeyNotFoundException is thrown, how do I see which key wasn't found?抛出 KeyNotFoundException 时,如何查看未找到哪个键?
【发布时间】:2011-11-05 23:54:40
【问题描述】:

System.Collections.Generic.Dictionary 正在抛出 KeyNotFoundException,但我看不出应该丢失了哪个键。如何确定?

【问题讨论】:

  • 字典的键可以是任何类型。在异常消息中使用 ToString() 会使用户的眼睛流血,增加太多噪音。也没有要求总是覆盖 ToString() 以显示有用的内容。
  • @HansPassant:它仍然可能是异常类的属性。
  • 根据您要执行的操作,检查字典上的 TryGetValue 方法可能是个好主意。您可以使用它来创建安全的字典查找并节省因捕获异常而产生的开销。
  • @DanielHilgarth:在某些情况下,字典键可能包含机密数据,并且代码应该假设异常对象可能以对任何人都可见的方式记录。虽然类提供构造函数参数可能很方便,该参数表示传递给它的数据应该被视为非机密数据并且可以安全地暴露在异常中,但类通常不应该在没有这样做的理由的情况下做出这样的假设.
  • if (!dictionary.TryGetValue(key, out value)) throw new KeyNotFoundException(key);

标签: c# .net exception .net-4.0 keynotfoundexception


【解决方案1】:

你会认为他们可以将尝试的键添加到 exception.data

这就是我所做的

        public static void SetGlobals()
    {
        string currentKey = "None";
        try
        {
            currentKey = "CurrentEnvironment";
            Globals.current_environment = Settings[currentKey];
            currentKey = "apiUrl";
            Globals.api_url = Settings[currentKey];
            currentKey = "whatever";
            Globals.whatever= Settings[currentKey];

            if (AppDomain.CurrentDomain.GetData(".devEnvironment") as bool? == true)
                Globals.api_url = "http://localhost:59164/api";
        }
        catch(KeyNotFoundException)
        {
            DBClass.logEvent("Error", "AppSettings", "Missing Setting: " + currentKey);
        }
    }

【讨论】:

    【解决方案2】:

    System.Collections.Generic.KeyNotFoundException 已被抛出

    如果您将 DotNet Core 与 Xamarin Studio 一起使用并收到此错误,您可以检查密钥是否存在,并满足以下条件:

    if (Application.Current.Properties.ContainsKey("userCredentials")) {
        //now process...
    }
    

    【讨论】:

      【解决方案3】:

      自定义异常:

      class WellknownKeyNotFoundException : KeyNotFoundException
      {
          public WellknownKeyNotFoundException(object key, string message)
              : this(key, message, null) { }
      
          public WellknownKeyNotFoundException(object key, string message, Exception innerException)
              : base(message, innerException)
          {
              this.Key = key;
          }
      
          public object Key { get; private set; }
      }
      

      方便的扩展方法:

      public TValue GetValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
      {
          try
          {
              return dic[key];
          }
          catch (KeyNotFoundException ex)
          {
              throw new WellknownKeyNotFoundException((object)key, ex.InnerException);
          }
      }
      

      用法:

      var foo = new Foo();
      var bar = new Bar();
      
      IDictionary<Foo, Bar> dic = new Dictinary<Foo, Bar>
      {
          { foo, bar }
      };
      
      try
      {
          dic.GetValue(foo);
      }
      catch (WellknownKeyNotFoundException ex)
      {
          var key = (Foo)ex.Key;
          Assert.AreEqual(foo, key); // should be
      }
      

      【讨论】:

      • 看来可以派上用场了。
      • 不要构建自己的扩展方法,而是使用 trygetvalue 来获得更干净的解决方案。
      【解决方案4】:

      如果您可以自定义声明字典的实现,您可以轻松地将 System.Collections.Generic.Dictionary 替换为自定义类型,引发更好的 KeyNotFoundException。虽然这与 abatishchev 的答案相似,但我不喜欢他介绍的扩展方法,因为这意味着我们有两种不同的方法来实现完全相同的事情。如果可能,应该避免这种情况。我通过使用“NiceDictionary”解决了这个问题,它可以像原来用作基类的字典一样使用。实现几乎是微不足道的:

      /// <summary>
      /// This is a nice variant of the KeyNotFoundException. The original version 
      /// is very mean, because it refuses to tell us which key was responsible 
      /// for raising the exception.
      /// </summary>
      public class NiceKeyNotFoundException<TKey> : KeyNotFoundException
      {
          public TKey Key { get; private set; }
      
          public NiceKeyNotFoundException(TKey key, string message)
              : base(message, null)
          {
              this.Key = key;
          }
      
          public NiceKeyNotFoundException(TKey key, string message, Exception innerException)
              : base(message, innerException)
          {
              this.Key = key;
          }
      }
      
      /// <summary>
      /// This is a very nice dictionary, because it throws a NiceKeyNotFoundException that
      /// tells us the key that was not found. Thank you, nice dictionary!
      /// </summary>
      public class NiceDictionary<TKey, TVal> : Dictionary<TKey, TVal>
      {
          public new TVal this[TKey key]
          {
              get
              {
                  try
                  {
                      return base[key];
                  }
                  catch (KeyNotFoundException knfe)
                  {
                      throw new NiceKeyNotFoundException<TKey>(key, knfe.Message, knfe.InnerException);
                  }
              }
              set
              {
                  try
                  {
                      base[key] = value;
                  }
                  catch (KeyNotFoundException knfe)
                  {
                      throw new NiceKeyNotFoundException<TKey>(key, knfe.Message, knfe.InnerException);
                  }
              }
          }
      }
      

      如前所述,您可以像使用原始字典一样使用它。由于重写了数组运算符 ([]),它可以神奇地工作。

      【讨论】:

      • 看起来 try/catch 在 setter 中是多余的,因为在使用新键的情况下应该创建新条目
      • 另外,在 getter 中而不是 try/catch/throw 中,最好使用 TryGetValue,如果找不到键则抛出异常。从性能的角度来看应该更好
      • 需要说明的是,任何通过接口访问NiceDictionary的代码都会找到原来的索引器,不会得到NiceKeyNotFoundException。这可以通过重新实现所有接口来解决,但代价是代码爆炸。
      【解决方案5】:

      使用调试器(如果需要,请检查 Debug->Exceptions 中的 ThrowOnCatch)并查看

      【讨论】:

        【解决方案6】:

        你不能只看异常。当异常被抛出时,你必须闯入调试器(Debug -> Exceptions... 在 Visual Studio 中)并查看已访问的键。或者,您可以在代码中捕获异常并将其打印出来(例如打印到控制台)。

        【讨论】:

        • 嗯,这是我的策略,但它无法准确指出原因,因为一个语句中有多个字典访问......我通过将语句分成几个较小的语句来解决它,但它是有点笨重。
        【解决方案7】:

        没有办法从异常中看出这一点。您需要为此实施自己的解决方案。

        【讨论】:

        • 感谢您澄清情况。不过有点奇怪,异常不能传达这个吗?这种设计有什么已知的理由吗?
        • @aknuds1:我不知道为什么要这样设计。我当然会包括钥匙。
        • @aknuds1 关键对象需要一个“可读的”ToString() 实现才能给出任何有用的结果。
        • @Magnus:不是真的。键可能是异常上的 object 类型的属性。这样至少处理异常的代码可以分析丢失的密钥。但是由于您提到的原因,它可能不应该包含在异常消息中。
        • 对于 new DateTime(a=50,b=50,c=50) 也是如此,它会抛出“无效日期”,但没有提及纯整数参数。它迫使我包装“新”调用,因为抛出的异常是无用的。这似乎是 .Net 代码中的某种策略或(不是)最佳实践。
        猜你喜欢
        • 1970-01-01
        • 2014-04-12
        • 1970-01-01
        • 1970-01-01
        • 2017-03-28
        • 1970-01-01
        • 2022-09-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多