65.9K
CodeProject 正在变化。 阅读更多。
Home

DiagnosticDictionary

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.11/5 (10投票s)

2008年11月19日

CPOL

2分钟阅读

viewsIcon

39266

downloadIcon

108

让“给定的键在字典中不存在”消息更具信息性。

引言

我希望我不是唯一一个经历过可怕的KeyNotFoundException消息的人

未处理的异常:System.Collections.Generic.KeyNotFoundException:给定的键在字典中不存在。

这让每个人都想知道,这个键是什么?这个字典是什么?

当然,程序员可以用try-catch块包装每一行使用字典索引器的代码,或者至少是某个外部方法,但即使这样,异常也不会告诉你导致异常的键。所以,这是一篇简短的文章,介绍了一个简单的实现,它覆盖了Dictionary<>类。我还探讨了扩展方法是否适合实现所需的行为。

DiagnosticDictionary 类

为了帮助解决这个问题,我创建了一个DiagnosticDictionary,它覆盖了(带有令人讨厌的“new”关键字)泛型字典的索引器。它捕获KeyNotFoundException,并尝试描述键和字典名称(可以在构造函数中提供)重新抛出它。作为奖励,我还添加了一个Tag属性,可用于将任何对象与字典关联。

实现

复制并粘贴以下代码,并将您的Dictionary构造函数替换为DiagnosticDictionary

using System;
using System.Collections.Generic;

namespace Clifton.Collections.Generic
{
  /// <summary>
  /// A dictionary with an indexer that produces an informative
  /// KeyNotFoundException message.
  /// </summary>
  public class DiagnosticDictionary<TKey, TValue> : Dictionary<TKey, TValue>
  {
    protected object tag;
    protected string name = "unknown";

    /// <summary>
    /// Gets/sets an object that you can associate with the dictionary.
    /// </summary>
    public object Tag
    {
      get { return tag; }
      set { tag = value; }
    }

    /// <summary>
    /// The dictionary name. The default is "unknown". 
    /// Used to enhance the KeyNotFoundException.
    /// </summary>
    public string Name
    {
      get { return name; }
      set { name = value; }
    }

    /// <summary>
    /// Parameterless constructor.
    /// </summary>
    public DiagnosticDictionary()
    {
    }

    /// <summary>
    /// Constructor that takes a name.
    /// </summary>
    public DiagnosticDictionary(string name)
    {
      this.name = name;
    }

    /// <summary>
    /// Indexer that produces a more useful KeyNotFoundException.
    /// </summary>
    public new TValue this[TKey key]
    {
      get
      {
        try
        {
          return base[key];
        }
        catch (KeyNotFoundException)
        {
          throw new KeyNotFoundException("The key '" + key.ToString() + 
             "' was not found in the dictionary '"+name+"'");
        }
      }

      set { base[key] = value;}
    }
  }
}

给定测试代码

DiagnosticDictionary<string, string> d2 = 
       new DiagnosticDictionary<string, string>("Test");
string b = d2["b"];

DiagnosticDictionary将抛出一个更有用的异常

Unhandled Exception: System.Collections.Generic.KeyNotFoundException: The key 
   'b' was not found in the dictionary 'Test'

请注意,它现在会告诉你键和字典名称。

替代实现:扩展方法

以下说明了使用扩展方法(由 CPian Wouter Ballet 提供,请参阅下面的文章评论)

public static class DiagnosticDictionary
{
  public static TValue DiagItem<TKey, TValue>(this IDictionary<TKey, 
                                              TValue> d, TKey key)
  {
    try
    {
      return d[key];
    }
    catch (KeyNotFoundException)
    {
      throw new KeyNotFoundException("The key '" + key + 
          "' was not found in the dictionary.");
    }
  }
}

但是,您必须更改索引键的方式

Dictionary<string, string> d2 = new Dictionary<string, string>();
string b = d2.DiagItem("b");

所以,我认为这更多的是一个架构选择,需要在项目早期做出。我认为,如果您要启动一个项目,那么使用扩展方法比重构现有项目中的所有索引器更有意义。

为什么不使用 DiagnosticDictionary?

首先是通过DiagnosticDictionary索引器的性能损失,然后它会调用基类索引器。

其次是安全性。假设,你定义了一个像这样的字典(充其量是可疑的,但这是一个例子)

Dictionary<Hash password, Rights rights>

你当然不希望异常发出密码键!

关于 ToString() 呢?

调用key.ToString()对于值类型和字符串来说很好。如果你的键有更复杂的结构或类,那么你可以考虑重写ToString()方法来提供对 struct/class 内容的更具信息性的描述。

结论

希望人们会发现这个实现和我的客户的 QA 人员、数据库管理员、开发人员,甚至我自己一样有用。 :)

特别感谢 CPian Wouter Ballet 向我展示如何在扩展方法中使用泛型。

© . All rights reserved.