What Is "Best Practice" for Comparing Two Instances of a Reference Type

What is Best Practice For Comparing Two Instances of a Reference Type?

It looks like you're coding in C#, which has a method called Equals that your class should implement, should you want to compare two objects using some other metric than "are these two pointers (because object handles are just that, pointers) to the same memory address?".

I grabbed some sample code from here:

class TwoDPoint : System.Object
{
public readonly int x, y;

public TwoDPoint(int x, int y) //constructor
{
this.x = x;
this.y = y;
}

public override bool Equals(System.Object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}

// If parameter cannot be cast to Point return false.
TwoDPoint p = obj as TwoDPoint;
if ((System.Object)p == null)
{
return false;
}

// Return true if the fields match:
return (x == p.x) && (y == p.y);
}

public bool Equals(TwoDPoint p)
{
// If parameter is null return false:
if ((object)p == null)
{
return false;
}

// Return true if the fields match:
return (x == p.x) && (y == p.y);
}

public override int GetHashCode()
{
return x ^ y;
}
}

Java has very similar mechanisms. The equals() method is part of the Object class, and your class overloads it if you want this type of functionality.

The reason overloading '==' can be a bad idea for objects is that, usually, you still want to be able to do the "are these the same pointer" comparisons. These are usually relied upon for, for instance, inserting an element into a list where no duplicates are allowed, and some of your framework stuff may not work if this operator is overloaded in a non-standard way.

Comparing a collection of reference type objects for equality ignoring order of items in collection

@Neil answer is correct, except that it won't work with reference types (string are an exception since they are immutable).

Item is a class so it is a reference type.

Except uses the default equality comparer to compare the elements. Since Item is a class, it will be compared by reference, which is not the desired solution. So we need to bypass the default comparison, with a custom equality comparer. An overload of Except exists for that purpose.

You will need to create a type that implements IEqualityComparer<Item> and pass an instance of that type to Except.

See: Except overload documentation and IEqualityComparer documentation

Here is an example that you can run in Linqpad. It uses both Except overloads. One return false, the other true:

void Main()
{
var basket1 = new Basket()
{
items = new Item[]
{
new Item() { name = "bread", price = 1.5 },
new Item() { name = "butter", price = 2 }
}
};

var basket2 = new Basket()
{
items = new Item[]
{
new Item() { name = "butter", price = 2 },
new Item() { name = "bread", price = 1.5 }
}
};

var isIdenticalByReference = (!(basket1.items.Except(basket2.items).Any())); // false
isIdenticalByReference.Dump();

var isIdenticalWithCustomEqualityComparer = (!(basket1.items.Except(basket2.items, new ItemEqualityComparer()).Any())); // true
isIdenticalWithCustomEqualityComparer.Dump();
}

// You can define other methods, fields, classes and namespaces here

public class Item
{
public string name { get; set; }
public double price { get; set; }

public int GetHashCode(object obj)
{
return (name?.GetHashCode() ?? 0) ^ price.GetHashCode();
}
}

public class ItemEqualityComparer : IEqualityComparer<Item>
{
public bool Equals(Item I1, Item I2)
{
if (I2 == null && I1 == null)
return true;
else if (I1 == null || I2 == null)
return false;
else return I1.name == I2.name && I1.price == I2.price;
}

public int GetHashCode(Item item)
{
return (item.name?.GetHashCode() ?? 0) ^ item.price.GetHashCode();
}
}

public class Basket
{
public Item[] items;
}

C# operator== : what is standard practice (reference/value equality)?


The question was whether it is commonly acceptable to override
operator== to test for value equality

It depends on the object, if the object is immutable then you can override == operator, otherwise not. (Remember they are just guidelines).

See: Guidelines for Overriding Equals() and Operator == (C# Programming Guide)

By default, the operator == tests for reference equality by
determining whether two references indicate the same object.
Therefore, reference types do not have to implement operator == in
order to gain this functionality. When a type is immutable, that
is, the data that is contained in the instance cannot be changed,
overloading operator == to compare value equality instead of reference equality can be useful because, as immutable objects, they
can be considered the same as long as they have the same value. It
is not a good idea to override operator == in non-immutable types.

Comparing two objects with == operator


I am not sure why it changes when you change it from 10 to 1

I believe this is an implementation detail and you should not rely on it (will try to find something in the specs) but some positive single digit numbers are cached in int.ToString implementation for .NET Core. Here is excerpt from UInt32ToDecStr which is called internally by int.ToString:

// For single-digit values that are very common, especially 0 and 1, just return cached strings.
if (bufferLength == 1)
{
return s_singleDigitStringCache[value];
}

As for equality - please check:

  1. C# difference between == and Equals().
  2. String interning in .Net Framework. (compiler will intern string literals, so all of them will point to the same address in memory)
  3. Using type dynamic

UPD:

Was not able to find anything in specs, but next code behaves differently in .NET Framework and .NET 6 (former one prints 11 times False and the latter prints 10 times True and one False):

var dict = new Dictionary<int, string>()
{
{0, "0"},
{1, "1"},
{2, "2"},
{3, "3"},
{4, "4"},
{5, "5"},
{6, "6"},
{7, "7"},
{8, "8"},
{9, "9"},
{10, "10"},
};

foreach(var kvp in dict)
{
Console.WriteLine(object.ReferenceEquals(kvp.Key.ToString(), kvp.Value));
}

UPD2:

The caching was introduced for performance reasons by this PR and is mentioned in Performance Improvements in .NET Core 3.0 blogpost:

In some sizeable web applications, we found that a large number of strings on the managed heap were simple integral values like “0” and “1”. And since the fastest code is code you don’t need to execute at all, why bother allocating and formatting these small numbers over and over when we can instead just cache and reuse the results (effectively our own string interning pool)? That’s what PR dotnet/coreclr#18383 does, creating a small, specialized cache of the strings for “0” through “9”, and any time we now find ourselves formatting a single-digit integer primitive, we instead just grab the relevant string from this cache.

private int _digit = 4;

[Benchmark]
public string SingleDigitToString() => _digit.ToString();










































MethodToolchainMeanErrorStdDevRatioGen 0Gen 1Gen 2Allocated
SingleDigitToStringnetcoreapp2.117.72 ns0.3273 ns0.3061 ns1.000.015232 B
SingleDigitToStringnetcoreapp3.011.57 ns0.1750 ns0.1551 ns0.65

Comparing VALUE and REFERENCE of types

Equals and == will compare by reference by default if they're not overriden / overloaded in a subclass. ReferenceEquals will always compare by reference.

Strings are a confusing data type to use for experimenting with this, because they overload == to implement value equality; also, since they're immutable, C# will generally reuse the same instance for the same literal string. In your code, str and str2 will be the same object.

Comparing two instances of a class

You should implement the IEquatable<T> interface on your class, which will allow you to define your equality-logic.
Actually, you should override the Equals method as well.

public class TestData : IEquatable<TestData>
{
public string Name {get;set;}
public string type {get;set;}

public List<string> Members = new List<string>();

public void AddMembers(string[] members)
{
Members.AddRange(members);
}

// Overriding Equals member method, which will call the IEquatable implementation
// if appropriate.

public override bool Equals( Object obj )
{
var other = obj as TestData;
if( other == null ) return false;

return Equals (other);
}

public override int GetHashCode()
{
// Provide own implementation
}


// This is the method that must be implemented to conform to the
// IEquatable contract

public bool Equals( TestData other )
{
if( other == null )
{
return false;
}

if( ReferenceEquals (this, other) )
{
return true;
}

// You can also use a specific StringComparer instead of EqualityComparer<string>
// Check out the specific implementations (StringComparer.CurrentCulture, e.a.).
if( EqualityComparer<string>.Default.Compare (Name, other.Name) == false )
{
return false;
}
...

// To compare the members array, you could perhaps use the
// [SequenceEquals][2] method. But, be aware that [] {"a", "b"} will not
// be considerd equal as [] {"b", "a"}

return true;

}

}

Proper way to compare two objects of different types

You absolutely should not do it this way. Instead, as you suggested, you should simply write this logic as a separate method.

At the very least, code like this would be confusing, which leads to bugs, bugs that are usually hard to fix because no one else looking at the code (or you in six months) would ever guess someone would do something like this.

More problematic is that as most humans understand the concept of equality, it's reflexive. That is, if object A equals object B, then object B equals object. But if you set up an override like this, that won't be true. Instead, even if a.Equals(b) is true, it's possible that b.Equals(a) won't be.

Again, at the very least this is confusing. But it is even worse if you wind up trying to use this kind of implementation in, for example, a hash table or try to find an element in a list or other collection, that sort of thing. These are scenarios where the reflexive nature of equality is assumed and relied upon, and code like the above simply won't even work.

Please, for your own sanity and for the sanity of others, just write a regular method to make this determination, such as LightweightMatchesServiceObject() or something clear and understandable like that.



Related Topics



Leave a reply



Submit