如何实现用作字典键的结构体

4

我正在将自定义结构体作为字典的键使用:


    public struct TV
    {
        public int B;
        public int BC;
        public int P;
        public int PO;
        public int PC;
        public int W;
        public int WO;
        public int WC;
        public int R;
        public int RO;
        public int RC;
        public int G;
        public int GO;
        public int GC;
        public int GW;
        public int GWO;
        public int GWC;

        public TV(int b, int bC, int p, int po, int pC, int w, int wo, int wC, int r, int ro, int rC, int g, int go, int gC, int gw, int gwo, int gwC)
        {
            B = b;
            BC = bC;
            P = p;
            PO = po;
            PC = pC;
            W = w;
            WO = wo;
            WC = wC;
            R = r;
            RO = ro;
            RC = rC;
            G = g;
            GO = go;
            GC = gC;
            GW = gw;
            GWO = gwo;
            GWC = gwC;
    }

    }


然而,由于我使用.containskey和getkey,每帧都会产生数百万次垃圾回收分配,这是一个问题。我已经研究了这个问题,我知道这与结构体的不正确装箱有关,因为没有实现IEquatable和一些方法的重写,如equals()和getHashCode。
我看到了一些实现的例子,但只找到了两个或三个变量的小结构体的例子,因为我的结构体有17个值,所以我不知道该如何实现它,也因为我不理解哈希码的工作原理,如果有人能指导我正确的方向,我将非常感激,我应该添加什么来使这个结构体可用作字典键?

使用作为键意味着进行大量比较。而结构体的默认比较器使用反射。请提供一个无需反射的变体,这样可以加快速度。 - Christopher
另一个值得知道的事情是,字典是通用哈希列表。对象实现了“GetHashCode()”。然而,默认实现将返回一个似乎从引用派生的数字。同样,在速度方面,重写GetHashCode应该对您有所帮助。 - Christopher
一个好的哈希基于这些值可以是什么。如果它们都可以是任何int值,那么将它们组合起来的标准哈希应该是可以的。但是,如果其中一些值具有较小的范围,您可以做得更好,甚至可能想出更好的结构。 - juharr
完全不清楚“TV”代表什么。它有16或17个字段,全部为int类型,没有命名规范可供解析。老实说,看起来大部分情况下TV应该是自己的字典。无论如何,这种类型的设计确实存在问题,特别是如果它被用作键的话。 - Christopher
作为附注:可变值类型的处理方式...比较棘手;您可能需要考虑使用“只读结构体”;这样也可以在构造期间预先计算哈希码,而不是按需计算。 - Marc Gravell
显示剩余4条评论
1个回答

10

关键在于:

  1. 实现 IEquatable<TV> 接口(它将通过“限制”调用而不是装箱调用来调用)
  2. 使用适当的哈希函数实现 GetHashCode(),对您想要比较的任何字段进行哈希处理
  3. 使用与 GetHashCode() 对齐的相等性检查实现 Equals(TV)
  4. Equals(object) 实现为 => obj is TV typed && Equals(typed);

这应该避免所有的装箱和反射。


这里是一种实现方法:

public struct TV : IEquatable<TV>
{

    public override string ToString() => nameof(TV);

    public int B;
    public int BC;
    public int P;
    public int PO;
    public int PC;
    public int W;
    public int WO;
    public int WC;
    public int R;
    public int RO;
    public int RC;
    public int G;
    public int GO;
    public int GC;
    public int GW;
    public int GWO;
    public int GWC;

    public TV(int b, int bC, int p, int po, int pC, int w, int wo, int wC, int r, int ro, int rC, int g, int go, int gC, int gw, int gwo, int gwC)
    {
        B = b;
        BC = bC;
        P = p;
        PO = po;
        PC = pC;
        W = w;
        WO = wo;
        WC = wC;
        R = r;
        RO = ro;
        RC = rC;
        G = g;
        GO = go;
        GC = gC;
        GW = gw;
        GWO = gwo;
        GWC = gwC;
    }

    public override bool Equals(object obj) => obj is TV other && Equals(other);

    public bool Equals(TV other)
    {
        return B == other.B &&
               BC == other.BC &&
               P == other.P &&
               PO == other.PO &&
               PC == other.PC &&
               W == other.W &&
               WO == other.WO &&
               WC == other.WC &&
               R == other.R &&
               RO == other.RO &&
               RC == other.RC &&
               G == other.G &&
               GO == other.GO &&
               GC == other.GC &&
               GW == other.GW &&
               GWO == other.GWO &&
               GWC == other.GWC;
    }

    public override int GetHashCode()
    {
        var hash = new HashCode();
        hash.Add(B);
        hash.Add(BC);
        hash.Add(P);
        hash.Add(PO);
        hash.Add(PC);
        hash.Add(W);
        hash.Add(WO);
        hash.Add(WC);
        hash.Add(R);
        hash.Add(RO);
        hash.Add(RC);
        hash.Add(G);
        hash.Add(GO);
        hash.Add(GC);
        hash.Add(GW);
        hash.Add(GWO);
        hash.Add(GWC);
        return hash.ToHashCode();
    }
}

谢谢您的回复,您能否大致展示一下我应该如何在这种情况下实现GetHashCode()方法? - flask
您可能还需要考虑将所有公共字段设置为只读,因为结构体应该是不可变的,并且在 GetHashCode 中使用的字段也应该是不可变的。 - Rufus L
@RufusL 确实如此;请参见问题中类似的评论。 - Marc Gravell
现在,由于新功能的支持,您只需简单地声明一个 public record struct TV(...) - pampua84

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接