在类中具有GUID私有属性以便在GetHashCode重写中使用它可以吗

本文关键字:重写 GUID 属性 GetHashCode | 更新日期: 2023-09-27 17:54:38

在类中具有GUID私有属性以便在GetHashCode重写中使用它可以吗?

类似于:

public class Voucher : IComparable<Voucher>, IComparable, IEquatable<Voucher>
{
    private Guid? _guid;

    private Guid Guid
    {
        get
        {
            return _guid ?? (_guid = Guid.NewGuid()).GetValueOrDefault();
        }
    }
    public int Id { get; private set; }
    public string Number { get; private set; }
    public DateTime Date { get; private set; }

    public Voucher(string number, DateTime date)
    {
        Number = number;
        Date = date;
    }
    public Voucher(int id, string number, DateTime date)
        : this(number, date)
    {
        Id = id;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Voucher);
    }
    public override int GetHashCode()
    {
        return Guid.GetHashCode();
    }
    public override string ToString()
    {
        return String.Format("[{0}] - [{1:dd/MM/yyyy}]", Number, Date);
    }

    #region IComparable<Voucher> Members
    public int CompareTo(Voucher other)
    {
        if (other == null)
            return -1;
        if (Date != other.Date)
            return Date.CompareTo(other.Date);
        else
            return Number.CompareTo(other.Number);
    }
    #endregion
    #region IComparable Members
    public int CompareTo(object obj)
    {
        return CompareTo(obj as Voucher);
    }
    #endregion
    #region IEquatable<Voucher> Members
    public bool Equals(Voucher other)
    {
        if (other != null)
            return (Number == other.Number) && (Date == other.Date);
        return false;
    }
    #endregion
}

昨天我发现,为了覆盖GetHashCode,我们必须只使用类的不可变成员/字段。

在我的许多情况下,这只是由SqlServer标识生成的Id,对于新实例,这是0。

因此,对于许多新对象(未持久化到数据库,因此Id为0(,对象哈希代码是相同的。对的

像上面的例子一样使用GUID是一个解决方案吗?谢谢

编辑评论后分类

所以在你的评论之后,我把它改为:

public class Voucher : IComparable<Voucher>, IComparable, IEquatable<Voucher>
    {
        public int Id { get; private set; }
        public string Number { get; private set; }
        public DateTime Date { get; private set; }

        public Voucher(string number, DateTime date)
        {
            Number = number;
            Date = date;
        }
        public Voucher(int id, string number, DateTime date)
            : this(number, date)
        {
            Id = id;
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as Voucher);
        }
        public override int GetHashCode()
        {
            return Number.GetHashCode() ^ Date.GetHashCode();
        }
        public override string ToString()
        {
            return String.Format("[{0}] - [{1:dd/MM/yyyy}]", Number, Date);
        }

        #region IComparable<Voucher> Members
        public int CompareTo(Voucher other)
        {
            if (other == null)
                return -1;
            if (Date != other.Date)
                return Date.CompareTo(other.Date);
            else
                return Number.CompareTo(other.Number);
        }
        #endregion
        #region IComparable Members
        public int CompareTo(object obj)
        {
            return CompareTo(obj as Voucher);
        }
        #endregion
        #region IEquatable<Voucher> Members
        public bool Equals(Voucher other)
        {
            if (other != null)
                return (Number == other.Number) && (Date == other.Date);
            return false;
        }
        #endregion
    }

我想这是可以的,因为凭单是不可变的。

但是,如果成员Number和Date不是不可变的,并且可以在类外访问-更改?那么解决方案是什么呢?仅仅记录类就足够了吗?比如"不能在依赖哈希代码的列表中使用"?

在类中具有GUID私有属性以便在GetHashCode重写中使用它可以吗

不,以这种方式使用GUID是不好的,因为它破坏了GetHashCode()的功能,即计算对象内容的哈希,如果两个对象具有相同的内容,它们将具有相同的哈希。

您应该像下面这个问题一样实现GetHashCode():SO-GetHashCode的最佳算法是什么?您应该在散列中考虑对象的全部内容。

来自上述链接的相关代码为:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

正如其他人所提到的,使用Guid是不必要的。但我想我理解在比较未受阻碍的物体方面的斗争。我们在比较对象时使用三个级别:

AreSame((=表示为内存中的同一空间。我们在这里并没有真正使用方法,因为'x==y'做得很好。

AreEqual((=对我们来说,相等是通过具有相同的Id来定义的,包括0。如果id是default(int(,那么我们将其称为"empty"。很多时候,我们使用方法"IsNullOrEmpty(("测试新对象,该方法很好地描述了一个不存在的对象,或者一个新的、尚未持久化的对象。

//querying distinct persisted vouchers
var vouchers = vouchers.Where(w=>!w.IsNullOrEmpty()).Distinct();

AreEquivalent((-这是基于对象的各个属性(例如复合键(,对对象来说非常主观。例如,如果您的号码/日期代表一张不同的代金券,那么它将用于对等。你可以在这里使用匿名对象或其他东西来保持它的清晰。

//(warning: handle nulls appropriately, ideally by creating a better equalitycomparer here.).
    public override bool AreEquivalent(Voucher voucher){
    var propsAsAnonymous = v=>new{v.Number,v.Date};
    return propsAsAnonymous(this).Equals(propsAsAnonymous(voucher));
    }

编号。

约定指定两个相等的对象应该具有相同的hashCode。

在您的情况下,如果您创建了两个内容相同的对象,那么两个实例的哈希代码将不同。这破坏了契约,并可能破坏依赖hashCode的组件的行为。

此外,当覆盖GetHashCode()时,还必须覆盖Equals(object)

重写EqualsGetHashCode时,应遵循的规则是,如果两个实例相等,则它们应具有相同的哈希代码。您为相等的实例创建了唯一的哈希代码,这违反了该规则。这将导致像DictionaryHashSet这样的集合出现问题,它们依赖于GetHashCode为相等的项目返回相同的值。

即使其他人都说这不好,我还是会选择"好吧,这取决于"。

GUID对分布式系统很有用。毕竟,它们是全局唯一的标识符,所以如果你在多个进程/实例/持久性等的边界上检查相等性,并在这些边界上传递对象,我想说,你做的是正确的。

我也经常使用(顺序的(GUID作为数据库的ID。虽然大多数DBA出于性能原因不喜欢这样做,但它的好处是在插入之前不需要检查,从而节省了网络往返时间。就我个人而言,我相信这是未来数据库密钥的"最佳实践"。尽管如此,我承认这是非常有争议的,而且目前我认为这不是一个好的做法。

也就是说,你可能不想做这些事情

如果你只是想在你的程序实例中检查平等性,你应该考虑你想要实现什么。如果要按数据库ID对实例进行分组(例如,检查冲突(,则需要使用键成员创建相等(在这种情况下,ID可能足够了,因为它似乎是1个数据库实例上的数据库记录(。

如果您在应用程序中想要唯一的对象,您可以自己实现相等(注意:Object的默认实现已经这样工作了(。方法是使用RuntimeHelpers.GetHashCode(this)Object.ReferenceEquals(this, o);。这基本上使用指针进行比较。

总结一下:你追求的是什么取决于实现。通常,你想要相等,因为你在填充一个DictionaryHashSet。这还需要覆盖EqualsGetHashCode。您应该使用的实现是在该上下文中最有意义的实现。