【问题标题】:Dictionary with class as Key以类为键的字典
【发布时间】:2018-02-11 21:43:30
【问题描述】:

我正在学习电子工程,并且是 C# 的初学者。我已经测量了数据,我想以二维方式存储它。我想我可以像这样制作Dictionary

Dictionary<Key, string> dic = new Dictionary<Key, string>(); 

这里的“Key”是我自己的类,有两个 int 变量。现在我想将数据存储在这个Dictionary 中,但到目前为止它不起作用。如果我想用特殊的 Key 读取数据,错误报告说,该 Key 在 Dictionary 中不可用。

这里是类键:

public partial class Key
{
    public Key(int Bahn, int Zeile) {
    myBahn = Bahn;
    myZeile = Zeile;

}
    public int getBahn()
    {
        return myBahn;
    }
    public int getZeile()
    {
        return myZeile;
    }
    private  int myBahn;
    private int myZeile;
}

为了测试它,我做了这样的事情:

获取元素:

Key KE = new Key(1,1);
dic.Add(KE, "hans");
...

取出元素:

Key KE = new Key(1,1);
monitor.Text = dic[KE];

有人有想法吗?

【问题讨论】:

  • 为什么不使用字符串作为键?你到底想在字典中存储什么?你能解释一下用例吗?
  • 是的。我有来自 3D 房间扫描仪的数据。扫描仪有两个轴。我想根据两个轴的位置存储消息。因此,如果轴一位于位置 15,轴二位于位置 30,我希望拥有密钥:15,30
  • 你在字符串值中存储了什么?
  • 这只是为了测试。我有另一个类,其中存储了 4 个双精度值。这将是我想要存储为字典值的数据。
  • 不确定这是否可行,但有很多简单的解决方法(例如:使用类名作为键)

标签: c# class dictionary key


【解决方案1】:

虽然您可以通过实现自己的 EqualsGetHashCode 来使用类作为键,但如果您还不熟悉 C#,我不建议您这样做。

这些方法将由 C# 内部库调用,它们希望它们完全按照规范工作,优雅地处理所有边缘情况。如果您在其中添加错误,您可能会遇到令人不快的挠头会议。

在我看来,使用已经支持散列和比较的经过验证、真实和测试的现有类型在现场创建密钥同样有效且简单。

根据您的角坐标,例如:

int Bahn = 15;
int Zeile = 30;

您可以使用字符串(例如"15,30"):

String Key (int Bahn, int Zeile) { return $"{Bahn},{Zeile}"; }

var myDict = new Dictionary<string, string>();
myDict.Add (Key(Bahn,Zeile), myString);

如果你需要更高效的东西,或者两个元素的元组(例如&lt;15,30&gt;):

Tuple<int,int> Key (int Bahn, int Zeile) { return Tuple.Create(Bahn,Zeile); }

var myDict = new Dictionary<Tuple<int, int>, string>();
myDict.Add (Key(Bahn,Zeile), myString);

如果范围足够小以适合 int(例如15+30*360),或者如果您需要更高效的东西,则只是两个角度的组合:

int Key (int Bahn, int Zeile) { return Bahn+360*Zeile; }

var myDict = new Dictionary<int, string>();
myDict.Add (Key(Bahn,Zeile), myString);

这似乎比:

   class Key {
      // truckloads of code to implement the class,
      // not to mention testing it thourougly, including edge cases
   }

    var myDict = new Dictionary<Key, string>();
    myDict.Add (new Key(Bahn,Zeile), myString);

密钥的可变性

另外,请注意,只要您的键用于索引条目,它们就必须是不可变的。

如果在使用该键添加元素后更改BahnZiel 的值,您的字典会很糟糕。

行为未定义,但如果内部库最终检测到不一致的状态(例如多个条目被同一个键索引),您很可能会丢失随机条目、导致内存泄漏并可能崩溃并引发异常。

例如:

var myKey = new Key(15, 30);

for (String data in row_of_data_sampled_every_10_degrees)
{
   myDict.Add (myKey, data); // myKey must remain constant until the entry is removed
   myKey.Bahn += 10;         // changing it now spells the death of your dictionary
}

关于散列角坐标的附注

现在要注意的是,为整数、字符串和元组提供的通用散列函数不太可能为您的特定数据集产生最佳结果。

我建议从一个简单的解决方案开始,如果您遇到实际性能问题,则只使用专门的代码。在这种情况下,您可能最好使用更适合空间索引的数据结构(在极坐标的情况下通常使用quadtree,如果您想从扫描仪数据重建 3D 模型,则使用octree)。

【讨论】:

  • 我真的很鼓励以前的解决方案,因为它教授了正确的技术,并且将成为未来的证明。我不确定一次性是否会成为未来的证明。我的建议是使用久经考验的真实集合和技术,让花哨、复杂的类像原始类一样平等和可比。
  • OP 示例对我来说看起来像是一个寻找问题的解决方案,而且是一个危险的解决方案。 “使用类作为键”一开始是个坏主意,所以我宁愿挑战这个想法,也不愿继续讨论通常的 IEquatable 重载。
  • 使用类作为键非常健壮并且有很多应用。除此之外,学习如何编写复杂的类是一个很好的 CS 练习,这些类可以像排序、比较等操作的原语一样,有时甚至是其他运算符重载。当您想要创建可以与标准模板库无缝工作的类时,这种技术在 C++ 中非常流行。正如您所说,您的里程可能会有所不同。
  • 在我看来,在 OP 示例中,这是灾难的根源。无论如何...
【解决方案2】:

您需要在自己的类中重写方法 GetHashCodeEquals 才能将其用作键。

class Foo 
{ 
    public string Name { get; set;} 
    public int FooID {get; set;}
    public override int GetHashCode()             
    {  
           return FooID; 
    }
     public override bool Equals(object obj) 
    { 
             return Equals(obj as Foo); 
    }

    public bool Equals(Foo obj)
     { 
          return obj != null && obj.FooID == this.FooID; 
     }
}

【讨论】:

  • 好的,你能给我发个例子吗?
  • 在我的回答中添加了一个例子
  • 谢谢。但我不能在我的课堂上实现它。你能解释一下如果我覆盖这些方法会发生什么吗?如果你的 Foo 类中有两个 int 变量会怎样?比如:类 Foo { public int Name { get;设置;} 公共 int FooID {get;设置;}
  • 默认情况下,调用来自object 类的GetHashCodeEquals 方法,以便将键与您在字典中搜索的值进行比较。这不会为您提供所需的结果,因为不会根据您的自定义类进行比较。当您覆盖这些方法时,您可以自定义上述比较发生的方式。只需从 GetHashCode 返回一个使对象唯一的值,然后在 Equals 内对类的所有属性进行相等性检查
猜你喜欢
  • 1970-01-01
  • 2015-09-03
  • 1970-01-01
  • 2017-05-06
  • 2013-12-03
  • 2017-04-25
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
相关资源
最近更新 更多