c++ ##_##_在C#中实现价值平等

c++ ##_##

screenshot

介绍 (Introduction)

This article endeavors to demonstrate value equality semantics in C# using various techniques.

本文致力于使用各种技术来演示C#中的值相等语义。

背景 (Background)

Reference equality and value equality are two different ways to determine the equality of an object.

引用相等和值相等是确定对象相等的两种不同方法。

With reference equality, two objects are compared by memory address. If both objects point to the same memory address, they are equivalent. Otherwise, they are not. Using reference equality, the data the object holds is not considered. The only time two objects are equal is if they actually refer to the same instance.

在引用相等的情况下,通过内存地址比较两个对象。 如果两个对象都指向相同的内存地址,则它们是等效的。 否则,它们不是。 使用引用相等性,不考虑对象保存的数据。 两个对象唯一相等的时间是它们是否实际引用同一实例。

Often, we would prefer to use value equality. With value equality, two objects are considered equal if all of their fields have the same data, whether or not they point to the same memory location. That means multiple instances can be equal to each other, unlike with reference equality.

通常,我们更喜欢使用价值平等。 在值相等的情况下,如果两个对象的所有字段都具有相同的数据,则无论它们是否指向相同的内存位置,都将被视为相等。 这意味着多个实例可以彼此相等,与引用相等不同。

.NET provides a couple of facilities for implementing value equality semantics, depending on how you intend to use it.

.NET提供了一些用于实现值相等语义的工具,具体取决于您打算如何使用它。

One way to do it is to overload the appropriate methods on the class itself. Doing so means the class will always use value semantics. This might not be what you want, as in general not only might you want to distinguish between instances, but also value semantics is more resource intensive. Often times however, this is exactly what you need. Use your best judgement.

一种方法是在类本身上重载适当的方法。 这样做意味着该类将始终使用值语义。 这可能不是您想要的,通常,您不仅可能要区分实例,而且值语义还会占用更多资源。 通常,这正是您所需要的。 用你最好的判断。

Another way to do it is to create a class that implements IEqualityComparer<T>. This will allow your class to be compared using value semantics within classes like Dictionary<TKey,TValue>, but normal comparisons will use reference equality. Sometimes, this is precisely what you need.

另一种方法是创建一个实现IEqualityComparer<T> 。 这将允许您使用类中的值语义(例如Dictionary<TKey,TValue>来比较您的类,但是常规比较将使用引用相等。 有时,这正是您所需要的。

We'll explore both mechanisms here.

我们将在这里探讨这两种机制。

编码此混乱 (Coding this Mess)

First, consider the employee class:

首先,考虑员工类别:

public class Employee
{
    public int Id;
    public string Name;
    public string Title;
    public DateTime Birthday;
}

As you can see, this is a very simple class that represents a single employee. By default, classes use reference equality semantics, so in order to do value semantics we'll need to do additional work.

如您所见,这是一个非常简单的类,代表一个员工。 默认情况下,类使用引用相等语义,因此为了执行值语义,我们需要做其他工作。

We can use value semantics with this, or any class by creating a class implementing IEqualityComparer<T>:

通过创建实现IEqualityComparer<T>的类,我们可以在此类或任何类中使用值语义:

// a class for comparing two employees for equality
// this class is used by the framework in classes like
// Dictionary<TKey,TValue> to do key comparisons.
public class EmployeeEqualityComparer : IEqualityComparer<Employee>
{
    // static singleton field
    public static readonly EmployeeEqualityComparer Default = new EmployeeEqualityComparer();
    // compare two employee instances for equality
    public bool Equals(Employee lhs,Employee rhs)
    {
        // always check this first to avoid unnecessary work
        if (ReferenceEquals(lhs, rhs)) return true;
        // short circuit for nulls
        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
            return false;
        // compare each of the fields
        return lhs.Id == rhs.Id &&
            0 == string.Compare(lhs.Name, rhs.Name) &&
            0 == string.Compare(lhs.Title, rhs.Title) &&
            lhs.Birthday == rhs.Birthday;
    }
    // gets the hashcode for the employee
    // this value must be the same as long
    // as the fields are the same.
    public int GetHashCode(Employee lhs)
    {
        // short circuit for null
        if (null == lhs) return 0;
        // get the hashcode for each field
        // taking care to check for nulls
        // we XOR the hashcodes for the 
        // result
        var result = lhs.Id.GetHashCode();
        if (null != lhs.Name)
            result ^= lhs.Name.GetHashCode();
        if (null != lhs.Title)
            result ^= lhs.Title.GetHashCode();
        result ^= lhs.Birthday.GetHashCode();
        return result;
    }
}

Once you've done that, you can then pass this class to, for example, a dictionary:

完成此操作后,您可以将此类传递给例如字典:

var d = new Dictionary<Employee, int>(EmployeeEqualityComparer.Default);

Doing this allows the dictionary to use value semantics for key comparisons. This means that the keys are considered based on the value of their fields rather than their instance identity/memory location. Note above we are using Employee as the dictionary key. I've often used equality comparer classes when I needed to use collections as keys in dictionaries. This is a reasonable application of it, as you normally do not want value semantics with collections, even if you need them in particular cases.

这样做可以使字典将值语义用于键比较。 这意味着,将根据键的字段值而不是其实例标识/内存位置来考虑键。 注意,上面我们使用Employee作为字典键。 当我需要使用集合作为字典中的键时,我经常使用相等比较器类。 这是它的合理应用,因为您通常不希望集合具有值语义,即使您在特定情况下也需要它们。

Moving on to the second method, implementing value semantics on the class itself:

继续第二种方法,在类本身上实现值语义:

// represents a basic employee
// with value equality 
// semantics
public class Employee2 : 
    // implementing this interface tells the .NET
    // framework classes that we can compare based on 
    // value equality.
    IEquatable<Employee2>
{
    public int Id;
    public string Name;
    public string Title;
    public DateTime Birthday;

    // implementation of 
    // IEqualityComparer<Employee2>.Equals()
    public bool Equals(Employee2 rhs)
    {
        // short circuit if rhs and this
        // refer to the same memory location
        // (reference equality)
        if (ReferenceEquals(rhs, this))
            return true;
        // short circuit for nulls
        if (ReferenceEquals(rhs, null))
            return false;
        // compare each of the fields
        return Id == rhs.Id &&
            0 == string.Compare(Name, rhs.Name) &&
            0 == string.Compare(Title, rhs.Title) &&
            Birthday == rhs.Birthday;
    }
    // basic .NET value equality support
    public override bool Equals(object obj)
        => Equals(obj as Employee2);
    // gets the hashcode based on the value
    // of Employee2. The hashcodes MUST be
    // the same for any Employee2 that
    // equals another Employee2!
    public override int GetHashCode()
    {
        // go through each of the fields,
        // getting the hashcode, taking
        // care to check for null strings
        // we XOR the hashcodes together
        // to get a result
        var result = Id.GetHashCode();
        if (null != Name)
            result ^= Name.GetHashCode();
        if (null != Title)
            result ^= Title.GetHashCode();
        result ^= Birthday.GetHashCode();
        return result;
    }
    // enable == support in C#
    public static bool operator==(Employee2 lhs,Employee2 rhs)
    {
        // short circuit for reference equality
        if (ReferenceEquals(lhs, rhs))
            return true;
        // short circuit for null
        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
            return false;
        return lhs.Equals(rhs);
    }
    // enable != support in C#
    public static bool operator !=(Employee2 lhs, Employee2 rhs)
    {
        // essentially the reverse of ==
        if (ReferenceEquals(lhs, rhs))
            return false;
        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
            return true;
        return !lhs.Equals(rhs);
    }
}

As you can see, this is a bit more involved. We have the Equals() and GetHashCode() methods which should be familiar, but we also have an Equals() overload and two operator overloads, and we implement IEquatable<Employee2>. Despite this extra code, the basic idea is the same as with the first method.

如您所见,这涉及更多。 我们有应该熟悉的Equals()GetHashCode()方法,但是我们也有Equals()重载和两个运算符重载, 并且实现了IEquatable<Employee2> 。 尽管有这些额外的代码,但基本思想与第一种方法相同。

We implement Equals(Employee2 rhs) and GetHashCode() almost the same way as we did in the first method, but we need to overload the other Equals() method and forward the call. In addition, we create two operator overloads for == and !=, duplicating the reference equality and null checks, but then forwarding to Equals().

我们实现Equals(Employee2 rhs)GetHashCode()几乎与第一种方法相同,但是我们需要重载其他Equals()方法并转发调用。 此外,我们为==!=创建了两个运算符重载,复制了引用相等性和null检查,然后转发给Equals()

Once we've implemented an object this way, the only way to do reference equality comparisons is by using ReferenceEquals(). Any other mechanism will give us value equality semantics, which is what we want.

一旦我们以这种方式实现了对象,唯一的引用相等比较方法就是使用ReferenceEquals() 。 任何其他机制都将为我们提供值相等的语义,这正是我们想要的。

Examples of using this can be found in the Main() method of the demo project's Program class:

可以在演示项目的Program类的Main()方法中找到使用此功能的示例:

static void Main(string[] args)
{
    // prepare 2 employee instances
    // with the same data
    var e1a = new Employee()
    {
        Id = 1,
        Name = "John Smith",
        Title = "Software Design Engineer in Test",
        Birthday = new DateTime(1981, 11, 19)
    };
    var e1b = new Employee()
    {
        Id = 1,
        Name = "John Smith",
        Title = "Software Design Engineer in Test",
        Birthday = new DateTime(1981, 11, 19)
    };
    // these will return false, since the 2 instances are different
    // this is reference equality:
    Console.WriteLine("e1a.Equals(e1b): {0}", e1a.Equals(e1b));
    Console.WriteLine("e1a==e1b: {0}", e1a==e1b);
    // this will return true since this class is designed
    // to compare the data in the fields:
    Console.WriteLine("EmployeeEqualityComparer.Equals(e1a,e1b): {0}",
        EmployeeEqualityComparer.Default.Equals(e1a, e1b));
    // prepare a dictionary:
    var d1 = new Dictionary<Employee, int>();
    d1.Add(e1a,0);
    // will return true since the dictionary has a key with this instance
    Console.WriteLine("Dictionary.ContainsKey(e1a): {0}", d1.ContainsKey(e1a));
    // will return false since the dictionary has no key with this instance
    Console.WriteLine("Dictionary.ContainsKey(e1b): {0}", d1.ContainsKey(e1b));
    // prepare a dictionary with our custom equality comparer:
    d1 = new Dictionary<Employee, int>(EmployeeEqualityComparer.Default);
    d1.Add(e1a, 0);
    // will return true since the instance is the same
    Console.WriteLine("Dictionary(EC).ContainsKey(e1a): {0}", d1.ContainsKey(e1a));
    // will return true since the fields are the same
    Console.WriteLine("Dictionary(EC).ContainsKey(e1b): {0}", d1.ContainsKey(e1b));

    // prepare 2 Employee2 instances
    // with the same data:
    var e2a = new Employee2()
    {
        Id = 1,
        Name = "John Smith",
        Title = "Software Design Engineer in Test",
        Birthday = new DateTime(1981, 11, 19)
    };
    var e2b = new Employee2()
    {
        Id = 1,
        Name = "John Smith",
        Title = "Software Design Engineer in Test",
        Birthday = new DateTime(1981, 11, 19)
    };
    // these will return true because they are overloaded
    // in Employee2 to compare the fields
    Console.WriteLine("e2a.Equals(e2b): {0}", e2a.Equals(e2b));
    Console.WriteLine("e2a==e2b: {0}", e2a == e2b);
    // prepare a dictionary:
    var d2 = new Dictionary<Employee2, int>();
    d2.Add(e2a, 0);
    // these will return true, since Employee2 implements
    // Equals():
    Console.WriteLine("Dictionary.ContainsKey(e2a): {0}", d2.ContainsKey(e2a));
    Console.WriteLine("Dictionary.ContainsKey(e2b): {0}", d2.ContainsKey(e2b));
}

兴趣点 (Points of Interest)

Structs do a kind of value equality semantics by default. They compare each field. This works until the fields themselves use reference semantics, so you may find yourself implementing value semantics on a struct anyway if you need to compare those fields themselves by value.

默认情况下, Structs执行一种值相等语义。 他们比较每个领域。 在字段本身使用引用语义之前,此方法一直有效,因此,如果您需要按值比较这些字段本身,则无论如何会发现自己在结构上实现了值语义。

翻译自: https://www.codeproject.com/Articles/5251448/Implementing-Value-Equality-in-Csharp

c++ ##_##

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值