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:
- C# difference between == and Equals().
- String interning in .Net Framework. (compiler will intern string literals, so all of them will point to the same address in memory)
- 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();
Method | Toolchain | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|
SingleDigitToString | netcoreapp2.1 | 17.72 ns | 0.3273 ns | 0.3061 ns | 1.00 | 0.0152 | – | – | 32 B |
SingleDigitToString | netcoreapp3.0 | 11.57 ns | 0.1750 ns | 0.1551 ns | 0.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
Reflection to Identify Extension Methods
Setting an Object to Null VS Dispose()
Getting the Http Referrer in ASP.NET
How to Use Webrequest to Access an Ssl Encrypted Site Using Https
Changing the User Agent of the Webbrowser Control
Xmlserializer Giving Filenotfoundexception at Constructor
Read SQL Table into C# Datatable
Impossible to Use Ref and Out for First ("This") Parameter in Extension Methods
Why Should You Remove Unnecessary C# Using Directives
Why Do C# Multidimensional Arrays Not Implement Ienumerable<T>
Reading PDF Content with Itextsharp Dll in Vb.Net or C#
How to Use Openfiledialog to Select a Folder
Coercing Floating-Point to Be Deterministic in .Net
Interprocess Communication for Windows in C# (.Net 2.0)
Asynchronous Locking Based on a Key
How to Do Pagination in ASP.NET MVC
How to Count Duplicates in List with Linq
A Field Initializer Cannot Reference the Non-Static Field, Method, or Property