C# 关于具有可变元素的字典/哈希集的思考

C# 关于具有可变元素的字典/哈希集的思考,c#,dictionary,hashset,mutable,C#,Dictionary,Hashset,Mutable,我非常喜欢字典和哈希集,因为它们允许快速检查包含性,对于字典,还允许通过键快速访问元素 它们的缺点是,它们只适用于(准)不可变对象:因为它们基于散列,所以我们必须保证散列代码(字典的键、散列集的元素)永远不会更改,否则它们将再也找不到元素 但有时我需要字典/哈希集的好处,并且仍然能够以某种方式更改键/元素,以便它们的哈希代码也可以更改。示例:我想在集合中存储二维点。二维点由两个int值组成,如果两个二维点的int值相等,则它们相等。我需要能够更改点的值,但是我仍然需要一种快速的方法来检查集合中是

我非常喜欢字典和哈希集,因为它们允许快速检查包含性,对于字典,还允许通过键快速访问元素

它们的缺点是,它们只适用于(准)不可变对象:因为它们基于散列,所以我们必须保证散列代码(字典的键、散列集的元素)永远不会更改,否则它们将再也找不到元素

但有时我需要字典/哈希集的好处,并且仍然能够以某种方式更改键/元素,以便它们的哈希代码也可以更改。示例:我想在集合中存储二维点。二维点由两个int值组成,如果两个二维点的int值相等,则它们相等。我需要能够更改点的值,但是我仍然需要一种快速的方法来检查集合中是否包含某个点

所以我在想办法让它发挥作用。我需要什么

  • 每当我想要更改元素的数据时,我都需要1。从字典/集合中删除元素,2。改变数据,3。再次将元素添加到字典/集合。然后hashcode和存储元素的bucket将始终同步
  • 我还需要一种机制来自动为所有字典/集合执行此操作。否则,如果我有100个字典/集合,其中包含一个将要更改的元素,我将需要跟踪它们并单独更新每个元素
以下是一些示例代码,以说明我计划如何针对二维点和集解决此问题:

interface IChangeable<T>
{
    event EventHandler<T> OnStartChange; // must be run before changing data
    event EventHandler<T> OnEndChange;  // must be run after changing data
}

class Point : IChangeable<Point>
{
    private int x;
    private int y;

    public Point(int a, int b) { x = a; y = b; }

    public event EventHandler<Point> OnStartChange;
    public event EventHandler<Point> OnEndChange;

    public int X
    {
        get => x;
        set
        {
            if (x.Equals(value)) // no change => no need to do anything
                return;

            OnStartChange?.Invoke(this, this); // remove current point from set
            x = value;
            OnEndChange?.Invoke(this, this); // add changed point to set
        }
    }

    public int Y { ... } // analogous to X

    public override int GetHashCode() { return x + 2 * y; }
    public override bool Equals(object obj)
    {
        Point other = obj as Point;
        if (other == null)
            return false;
        return (x.Equals(other.x) && y.Equals(other.y));
    }
}

class MySet<T> : ISet<T> where T : class, IChangeable<T>
{
    HashSet<T> internalSet;

    public MySet() { internalSet = new HashSet<T>(); }

    public bool Add(T item)
    {
        if (!internalSet.Add(item)) // if already contained, do nothing
            return false;

        item.OnStartChange += TakeOut; // else subscribe to data change events
        item.OnEndChange += PutIn;
        return true;
    }

    public bool Remove(T item)
    {
        if (!internalSet.Remove(item)) // if not contained, do nothing
            return false;

        item.OnStartChange -= TakeOut; // else unsubscribe to data change events
        item.OnEndChange -= PutIn;
        return true;
    }

    public bool Contains(T item) { return internalSet.Contains(item); }

    private void TakeOut(object obj, T elem) { internalSet.Remove(elem); }

    private void PutIn(object obj, T elem) { internalSet.Add(elem); }

    ... // implement the rest of ISet<T> interface
}
接口i可更改
{
必须在更改数据之前运行EventHandler OnStartChange;//事件
EventHandler OnEndChange;//必须在更改数据后运行
}
类点:i可更改
{
私人INTX;
私营企业;
公共点(inta,intb){x=a;y=b;}
公共事件事件处理程序OnStartChange;
公共事件处理程序OnEndChange;
公共整数X
{
get=>x;
设置
{
if(x.Equals(value))//无需更改=>无需执行任何操作
返回;
OnStartChange?.Invoke(this,this);//从集合中删除当前点
x=值;
OnEndChange?.Invoke(this,this);//将更改的点添加到集合
}
}
公共整数Y{…}//类似于X
public override int GetHashCode(){return x+2*y;}
公共覆盖布尔等于(对象对象对象)
{
其他点=obj作为点;
如果(其他==null)
返回false;
回报率(x.Equals(other.x)和&y.Equals(other.y));
}
}
类MySet:ISet其中T:class,i可更改
{
HashSet-internalSet;
public MySet(){internalSet=new HashSet();}
公共布尔添加(T项)
{
if(!internalSet.Add(item))//如果已包含,则不执行任何操作
返回false;
item.OnStartChange+=抽头;//否则订阅数据更改事件
item.OnEndChange+=普京;
返回true;
}
公共布尔删除(T项)
{
if(!internalSet.Remove(item))//如果未包含,则不执行任何操作
返回false;
item.OnStartChange-=抽头;//否则取消订阅数据更改事件
item.OnEndChange-=普京;
返回true;
}
public bool Contains(T项){return internalSet.Contains(项);}
私有void抽头(objectobj,T elem){internalSet.Remove(elem);}
private void PutIn(object obj,T elem){internalSet.Add(elem);}
…//实现ISet接口的其余部分
}
因此,我需要使用
MySet
而不是常规的
HashSet
,并且我需要为我要更改的每个数据段使用两个事件来构建setter(只要该数据段合并到
GetHashCode
Equals
方法中)

我的问题是:

您是否看到我的方法有任何错误或此方法有任何问题?

  • 代码可读性/可理解性问题
  • 实施问题
  • 性能问题
  • 正确性问题

应该注意的是,更改数据的情况不会经常发生。因此,我确信,能够快速检查包含性所带来的性能收益将超过每次数据更改后更新集合所带来的性能损失。

我认为基于事件的方法在这里似乎有点过头了。为什么不简单地使用
字典
并检查某个点是否与x匹配;y坐标存在,如果您需要更改它-只需删除它并添加一个新的。。。在Winforms中,类点是不可变的(以及字典中的KeyValuePair),这不是没有原因的。除非您有问题,否则这是一个需要解决的问题。对我来说,这种要求是一种异国情调。字典对可变值没有问题,只有键才有问题。但我为什么要变异密钥呢?它们通常不是数据的一部分,仅与字典一起使用,这意味着:用旧键删除条目、更改键、添加新条目。@Fabjan
Point
仅用于说明。我的实际数据与点无关,但它不适合一个小的代码示例。@Sinatr在我的情况下,我有一些对象,其中键实际上是值的一部分(在这种情况下,.NET提供了一个名为
KeyedCollection
,但我想从头开始,以便更容易地查找错误)。通常键是对象的名称,我需要能够重命名对象。