I've created a strongly-typed, immutable wrapper class for various string IDs that flow through our system
The abstract BaseId class:
(some error-checking and formatting omitted for brevity...)
public abstract class BaseId
{
// Gets the type name of the derived (concrete) class
protected abstract string TypeName { get; }
protected internal string Id { get; private set; }
protected BaseId(string id) { Id = id; }
// Called by T.Equals(T) where T is a derived type
protected bool Equals(BaseId other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return String.Equals(Id, other.Id);
}
// warning CS0660 (see comment #1 below)
//public override bool Equals(object obj) { return base.Equals(obj); }
public override int GetHashCode()
{
return TypeName.GetHashCode() * 17 + Id.GetHashCode();
}
public override string ToString()
{
return TypeName + ":" + Id;
}
// All T1 == T2 comparisons come here (where T1 and T2 are one
// or more derived types)
public static bool operator ==(BaseId left, BaseId right)
{
// Eventually calls left.Equals(object right), which is
// overridden in the derived class
return Equals(left, right);
}
public static bool operator !=(BaseId left, BaseId right)
{
// Eventually calls left.Equals(object right), which is
// overridden in the derived class
return !Equals(left, right);
}
}
My goal was keep as much of the implementation in the base class so that the derived classes would be small, consisting mostly/entirely of boilerplate code.
Example concrete DerivedId class:
Note that this derived type defines no additional state of its own. Its purpose is solely to create a strong type.
public sealed class DerivedId : BaseId, IEquatable<DerivedId>
{
protected override string TypeName { get { return "DerivedId"; } }
public DerivedId(string id) : base(id) {}
public bool Equals(DerivedId other)
{
// Method signature ensures same (or derived) types, so
// defer to BaseId.Equals(object) override
return base.Equals(other);
}
// Override this so that unrelated derived types (e.g. BarId)
// NEVER match, regardless of underlying Id string value
public override bool Equals(object obj)
{
// Pass obj or null for non-DerivedId types to our
// Equals(DerivedId) override
return Equals(obj as DerivedId);
}
// warning CS0659 (see comment #2 below)
//public override int GetHashCode() { return base.GetHashCode(); }
}
Each class is generating a compiler warning:
Not overriding Object.Equals(object o) in BaseId generates a compile warning:
warning CS0660: 'BaseId' defines operator == or operator != but does not override Object.Equals(object o)
But if I implement BaseId.Equals(object o), it'll just be to call the base class implementation in Object.Equals(object o). I don't see how this will ever get called anyway; it's always overridden in the derived class, and the implementation there doesn't call up to this implementation.
Not overriding BaseId.GetHashCode() in DerivedId generates a compile warning:
warning CS0659: 'DerivedId' overrides Object.Equals(object o) but does not override Object.GetHashCode()
This derived class has no additional state, so there's nothing for me to do in an implementation of DerivedId.GetHashCode(), except to call the base class implementation in BaseId.GetHashCode().
I can suppress the compiler warnings or just implement the methods and have them call the base class implementations, but I want to make sure I'm not missing something.
Is there something odd about the way I did this, or is this just one of those things that you have to do to suppress warnings on otherwise correct code?
The reason those are warnings rather than errors is that the code will still work (probably), but it might do things that you don't expect. The warning is a big red flag that says, "Hey! You might be doing something bad here. You might want to take another look at it."
As it turns out, the warning is right on.
In this particular case, it's possible that some code can call Object.Equals(object) on one of your BaseId objects. For example, somebody could write:
bool CompareThings(BaseId thing, object other)
{
return thing.Equals(other);
}
The compiler will generate a call to Object.Equals(object) because your BaseId type doesn't override it. That method will do the default comparison, which is the same as Object.ReferenceEquals(object). So you have two different meanings of Equals. You need to override Object.Equals(object) and have it call Equals(BaseId) after checking that the object being compared is indeed of type BaseId.
In the second case, you're right: there probably isn't a need to override GetHashCode, since the object doesn't define any new fields or do anything that changes the meaning of Equals. But the compiler doesn't know that. Sure, it knows that you didn't add any fields, but you did override Equals, meaning that you potentially changed the meaning of equality. And if you changed the meaning of equality, then you very likely changed (or should change) how hash codes are computed.
Not handling equality properly is a very common cause of mistakes when designing new types. It's a good thing that the compiler is overly cautious in this area.
It is generally not good for classes to have more than one overridable (virtual or abstract) Equals method. Either have derived classes override Equals(object) themselves, or else have a sealed base implementation of Equals(object) (and possibly GetHashCode()) chain to an abstract or virtual Equals(BaseId) (and possibly GetDerivedHashCode()). It's unclear what exactly your goal is, though I would suggest that if things always supposed to be equal if ID and type both match, and unequal if ID or type doesn't match, your base types shouldn't need to include any equality checking; simply have the base equality check test whether the types match (probably using GetType() rather than TypeName).
I should mention, btw, that I generally dislike classes which overload == and != unless they are supposed to fundamentally behave as values. In C#, the == operator can either call an overloaded equality-check operator or test reference equality; compare the effects of:
static bool IsEqual1<T>(T thing1, thing2) where T:class
{
return thing1 == thing2;
}
static bool IsEqual2<T>(T thing1, thing2) where T:BaseId
{
return thing1 == thing2;
}
The first method above will perform a reference equality test even if T overloads the equality-check operator. In the second, it will use BaseId's overload. Visually, it's not exactly clear that the BaseId constraint should have such an effect, but it does. In vb.net, there would be no confusion since vb.net would not allow the overloadable equality-test operator in the IsEqual1; if a reference-equality test was desired in that method (or in the second, for that matter), code would have to use the Is operator. Since C# uses the same token as both a reference-equality test and an overloadable equality test, however, the binding of the == token isn't always obvious.
Addressing issue #2 in the question:
Not overriding BaseId.GetHashCode() in DerivedId generates a compile warning:
Run the following code with the GetHashCode() method commented out, then again without commenting it out, you'll see that when there is no implementation of GetHashCode the set contains two instances of Person, but when you add the implementation of GetHashCode the set contains only one instance demonstrating that some operations/classes use GetHashCode for comparison.
class Program
{
static void Main(string[] args)
{
Person p1 = new Person() { FirstName="Joe", LastName = "Smith"};
Person p2 = new Person() { FirstName="Joe", LastName ="Smith"};
ISet<Person> set = new HashSet<Person>();
set.Add(p1);
set.Add(p2);
foreach (var item in set)
{
Console.WriteLine(item.FirstName);
}
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
if (obj == null) return false;
var that = obj as Person;
if (that == null) return false;
return
FirstName == that.FirstName &&
LastName == that.LastName;
}
public override int GetHashCode() //run the code with and without this method
{
int hashCode = 1938039292;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FirstName);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(LastName);
return hashCode;
}
}
Related
I have a class like this
public class 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);
}
}
I want to know if it is possible to directly compare to instances of this class to eachother and find out they are exactly the same? what is the mechanism? I am looking gor something like if(testData1 == testData2) //Do Something And if not, how to do so?
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;
}
}
One way of doing it is to implement IEquatable<T>
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);
}
public bool Equals(TestData other)
{
if (this.Name != other.Name) return false;
if (this.type != other.type) return false;
// TODO: Compare Members and return false if not the same
return true;
}
}
if (testData1.Equals(testData2))
// classes are the same
You can also just override the Equals(object) method (from System.Object), if you do this you should also override GetHashCode see here
There are three ways objects of some reference type T can be compared to each other:
With the object.Equals method
With an implementation of IEquatable<T>.Equals (only for types that implement IEquatable<T>)
With the comparison operator ==
Furthermore, there are two possibilities for each of these cases:
The static type of the objects being compared is T (or some other base of T)
The static type of the objects being compared is object
The rules you absolutely need to know are:
The default for both Equals and operator== is to test for reference equality
Implementations of Equals will work correctly no matter what the static type of the objects being compared is
IEquatable<T>.Equals should always behave the same as object.Equals, but if the static type of the objects is T it will offer slightly better performance
So what does all of this mean in practice?
As a rule of thumb you should use Equals to check for equality (overriding object.Equals as necessary) and implement IEquatable<T> as well to provide slightly better performance. In this case object.Equals should be implemented in terms of IEquatable<T>.Equals.
For some specific types (such as System.String) it's also acceptable to use operator==, although you have to be careful not to make "polymorphic comparisons". The Equals methods, on the other hand, will work correctly even if you do make such comparisons.
You can see an example of polymorphic comparison and why it can be a problem here.
Finally, never forget that if you override object.Equals you must also override object.GetHashCode accordingly.
I see many good answers here but just in case you want the comparison to work like
if(testData1 == testData2) // DoSomething
instead of using Equals function you can override == and != operators:
public static bool operator == (TestData left, TestData right)
{
bool comparison = true; //Make the desired comparison
return comparison;
}
public static bool operator != (TestData left, TestData right)
{
return !(left == right);
}
You can override the equals method and inside it manually compare the objects
Also take a look at Guidelines for Overloading Equals() and Operator ==
You will need to define the rules that make object A equal to object B and then override the Equals operator for this type.
http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx
First of all equality is difficult to define and only you can define as to what equality means for you
Does it means members have same value
Or they are pointing to same location.
Here is a discussion and an answer here
What is "Best Practice" For Comparing Two Instances of a Reference Type?
Implement the IEquatable<T> interface. This defines a generalized method that a value type or class implements to create a type-specific method for determining equality of instances. More information here:
http://msdn.microsoft.com/en-us/library/ms131187.aspx
I'm working in C#. I want to remove a Catalog (an object type i've defined earlier) from an ObservableCollection<Catalog>.
I know that when you call Remove,Contains or Add... the VM calls Equals to "locate" the object you want to add or remove. So here are my Catalog.Equals definitions :
public override bool Equals(object obj)
{
Catalog cat = (Catalog)obj;
return (this.Name.Equals(cat.Name));
}
public bool Equals(Catalog cat)
{
return (this.Name.Equals(cat.Name));
}
But when I execute myCollection.Remove(catlg) (catlg is of type Catalog), I get an InvalidCastException on the following line : Catalog cat = (Catalog)obj.
Why an invalid cast here ? And why Equals(Catalog cat) isn't called here, instead of Equals(object obj) definition ?
Of course, Remove doesn't work, even if Console.WriteLine(myCollection.Contains(catlg)) is true.
Try implementing the IEquatable<Catalog>.
And get rid of the override perhaps?
One Solution could be to use the linq extension methods.
var catalogToRemove = Catalogs.Single(p => p.Name == catalogName);
Catalogs.Remove(catalogToRemove);
An other could be to implement IEquatable
public class Catalog : IEquatable<Catalog>
{
public string Name { get; set; }
public bool Equals(Catalog other)
{
return Name == other.Name;
}
}
The second sulotion is good if you do not have the exact object you want to remove.
For example you could create a new Catalog object, set the name to the object you want to remove and use that new object in the Remove method of the collection. If you do not implement IEquatable this will not work since the new object is not exactly the same object that is contained in your collection.
Since you didn't implement IEquatable, the default for the Remove method is to use the Object.Equals method, and not your generic Equals(Catalog);
This is what the documentation states for IList.Remove:
If type T implements the IEquatable generic interface, the equality
comparer is the Equals method of that interface; otherwise, the
default equality comparer is Object.Equals.
This is what the MSDN documentation states about the IEquatable<T>.Equals:
If you implement Equals, you should also override the base class
implementations of Object.Equals(Object) and GetHashCode so that their
behavior is consistent with that of the IEquatable.Equals method.
If you do override Object.Equals(Object), your overridden
implementation is also called in calls to the static
Equals(System.Object, System.Object) method on your class. In
addition, you should overload the
There might be a compare somewhere in your code, where Equals(object) is called with another type that is not a Catalog, via the Equals(System.Object, System.Object), which causes the InvalidOperationException.
Use the as keyword instead of an explicit cast, and make sure you implement IEquatable<Catalog>:
public override bool Equals(object obj)
{
Catalog cat = obj as Catalog;
if (cat == null)
{
return;
}
return (this.Name.Equals(cat.Name));
}
You must implement GetHashCode (link)
public override bool Equals(object obj)
{
Catalog cat = obj as Catalog;
if (cat == null)
{
return false;
}
return (this.Name.Equals(cat.Name));
}
I'm using a different cast than you. With the as, you can check if it'is correct.
public bool Equals(Catalog cat)
{
return (this.Name.Equals(cat.Name));
}
public int GetHashCode(object obj)
{
// implement your hash code logic here
return obj.ToString().GetHashCode();
}
There is a passage from NHibernate documentation:
Note: if you define an ISet of composite elements, it is very important to implement Equals() and GetHashCode() correctly.
What does correctly mean there? Is it neccessary to implement those methods for all value objects in domain?
EXTENDING MY QUESTION
In the article Marc attached user Albic states:
It's actually very hard to implement GetHashCode() correctly because, in addition to the rules Marc already mentioned, the hash code should not change during the lifetime of an object. Therefore the fields which are used to calculate the hash code must be immutable.
I finally found a solution to this problem when I was working with NHibernate. My approach is to calculate the hash code from the ID of the object. The ID can only be set though the constructor so if you want to change the ID, which is very unlikely, you have to create a new object which has a new ID and therefore a new hash code. This approach works best with GUIDs because you can provide a parameterless constructor which randomly generates an ID.
I suddenly realized what I've got inside my AbstractEntity class:
public abstract class AbstractEntity<T> where T : AbstractEntity<T> {
private Nullable<Int32> hashCode;
public virtual Guid Id { get; protected set; }
public virtual Byte[] Version { get; set; }
public override Boolean Equals(Object obj) {
var other = obj as T;
if(other == null) {
return false;
}
var thisIsNew = Equals(this.Id, Guid.Empty);
var otherIsNew = Equals(other.Id, Guid.Empty);
if(thisIsNew && otherIsNew) {
return ReferenceEquals(this, other);
}
return this.Id.Equals(other.Id);
} // public override Boolean Equals(Object obj) {
public override Int32 GetHashCode() {
if(this.hashCode.HasValue) {
return this.hashCode.Value;
}
var thisIsNew = Equals(this.Id, Guid.Empty);
if(thisIsNew) {
this.hashCode = base.GetHashCode();
return this.hashCode.Value;
}
return this.Id.GetHashCode();
} // public override Int32 GetHashCode() {
public static Boolean operator ==(AbstractEntity<T> l, AbstractEntity<T> r) {
return Equals(l, r);
}
public static Boolean operator !=(AbstractEntity<T> l, AbstractEntity<T> r) {
return !Equals(l, r);
}
} // public abstract class AbstractEntity<T>...
As all components are nested within entities should I then implement Equals() and GetHashCode() for them?
Correctly means that GetHashCode returns the same hash code for the entities that are expected to be equal. Because equality of 2 entities is made by comparison of that code.
On the other side, that means that for entities that are not equal, the uniqueness of hash code has to be guaranteed, as much as it possible.
The documentation for Equals and GetHashCode explain this well and include specific guidance on implementation for value objects. For value objects, Equals is true if the objects are the same type and the public and private fields are equal. However, this explanation applies to framework value types and you are free to create your own Equals by overriding it.
GetHashCode has two rules that must be followed:
If two objects compare as equal, the GetHashCode method for each object must return the same value. However, if two objects do not
compare as equal, the GetHashCode methods for the two object do not
have to return different values.
The GetHashCode method for an object must consistently return the same hash code as long as there is no modification to the object state
that determines the return value of the object's Equals method. Note
that this is true only for the current execution of an application,
and that a different hash code can be returned if the application is
run again.
I know I can avoid boxing by adding my own Equals implementation.
public struct TwoDoubles
{
public double m_Re;
public double m_Im;
public TwoDoubles(double one, double two)
{
m_Re = one;
m_Im = two;
}
public override bool Equals(object ob)
{
return Equals((TwoDoubles)ob);
}
public bool Equals(TwoDoubles ob)
{
TwoDoubles c = ob;
return m_Re == c.m_Re && m_Im == c.m_Im;
}
}
I can't call this an override as much as an overload. By the magic of the runtime it does correctly call the correct Equals() implementation based on the type of the caller.
Why can't I override and change the parameter type to TwoDoubles and let boxing occur by the power of the runtime on an as needed basis? Is it because C# doesn't support parameter contravariance (if that's the reason then why is it not supported...seems a small step from object o = new TwoDoubles())?
UPDATE
Clarification: object is a part of the inheritance hierarchy of a struct. Why can we not specify a more derived type as a parameter to override an implementation from a less derived type? This would allow us to write:
public override bool Equals(TwoDoubles ob)
{
TwoDoubles c = ob;
return m_Re == c.m_Re && m_Im == c.m_Im;
}
Which should be called when the variable is a TwoDouble even if said variable has been boxed into an object type.
Why can't I override and change the parameter type to TwoDoubles?
Because that would not be typesafe!
class B
{
public virtual void M(Animal animal) { ... }
}
class D : B
{
public override void M(Giraffe animal) { ... }
}
B b = new D();
b.M(new Tiger());
And now you just passed a tiger to a method that actually only takes a giraffe!
Same thing in your case. You're overriding a method that takes any object with a method that can only take a struct; that's not typesafe.
Is it because C# doesn't support parameter type contravariance?
No, it is because you are asking for parameter type covariance, which is not typesafe.
C# does not support parameter type contravariance either, but that's not what you're asking for.
You can change the parameter (overload) for Equals, just as you've done, and boxing will occur as needed (i.e. whenever Equals(object) is called)
Because everything inherits (or implicitly via boxing) from object, you cannot stop people from being able to use Equals(object) on your type. However, by doing so, you get a boxed call.
If you are in control of the calling code, then always use your new overloaded option, i.e. Equals(TwoDouble)
Note, as one commentor already said, your code is slightly incorrect, do this instead:
public override bool Equals(object ob)
{
if (ob is TwoDoubles)
return Equals((TwoDoubles)ob);
else
return false;
}
public bool Equals(TwoDoubles c)
{
return m_Re == c.m_Re && m_Im == c.m_Im;
}
EDIT as has been wisely suggested, you should accomplish this same task but by using the IEquatable interface, in this case IEquatable<TwoDouble> (note, no code change necessary from what we've done as it matches the signature)
If it worked as you suggested, where public override bool Equals(TwoDoubles c) { return m_Re == c.m_Re && m_Im == c.m_Im; } was all that was necessary to override the Equals(object) method, what would this code do?
TwoDoubles td = new TwoDoubles();
object o = td;
bool b = o.Equals(new object()); // Equals(object) overridden with Equals(TwoDouble)
What should happen on line 3?
Should it call Equals(TwoDouble)? If so, with what parameter? A default TwoDouble with all zeros? That'd violate the rules surrounding equality.
Should it throw a cast exception? We followed the method signature properly, so that shouldn't happen.
Should it always return false? Now the compiler has to be aware of what the Equals method means, and will have to treat it differently than other methods.
There's no good way to implement this, and it quickly causes more problems than it solves.
I have a class it contains some string members, some double members and some array objects.
I create two objects of this class, is there any simplest, efficient way of comparing these objects and say their equal? Any suggestions?
I know how to write a compare function, but will it be time consuming.
The only way you can really do this is to override bool Object.Equals(object other) to return true when your conditions for equality are met, and return false otherwise. You must also override int Object.GetHashCode() to return an int computed from all of the data that you consider when overriding Equals().
As an aside, note that the contract for GetHashCode() specifies that the return value must be equal for two objects when Equals() would return true when comparing them. This means that return 0; is a valid implementation of GetHashCode() but it will cause inefficiencies when objects of your class are used as dictionary keys, or stored in a HashSet<T>.
The way I implement equality is like this:
public class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
if (other == null)
return false;
if (other == this)
return true; // Same object reference.
// Compare this to other and return true/false as appropriate.
}
public override bool Equals(Object other)
{
return Equals(other as Foo);
}
public override int GetHashCode()
{
// Compute and return hash code.
}
}
A simple way of implementing GetHashCode() is to XOR together the hash codes of all of the data you consider for equality in Equals(). So if, for example, the properties you compare for equality are string FirstName; string LastName; int Id;, your implementation might look like:
public override int GetHashCode()
{
return (FirstName != null ? FirstName.GetHashCode() : 0) ^
(LastName != null ? LastName.GetHashCode() : 0) ^
Id; // Primitives of <= 4 bytes are their own hash codes
}
I typically do not override the equality operators, as most of the time I'm concerned with equality only for the purposes of dictionary keys or collections. I would only consider overriding the equality operators if you are likely to do more comparisons by value than by reference, as it is syntactically less verbose. However, you have to remember to change all places where you use == or != on your object (including in your implementation of Equals()!) to use Object.ReferenceEquals(), or to cast both operands to object. This nasty gotcha (which can cause infinite recursion in your equality test if you are not careful) is one of the primary reasons I rarely override these operators.
The 'proper' way to do it in .NET is to implement the IEquatable interface for your class:
public class SomeClass : IEquatable<SomeClass>
{
public string Name { get; set; }
public double Value { get; set; }
public int[] NumberList { get; set; }
public bool Equals(SomeClass other)
{
// whatever your custom equality logic is
return other.Name == Name &&
other.Value == Value &&
other.NumberList == NumberList;
}
}
However, if you really want to do it right, this isn't all you should do. You should also override the Equals(object, object) and GetHashCode(object) methods so that, no matter how your calling code is comparing equality (perhaps in a Dictionary or perhaps in some loosely-typed collection), your code and not reference-type equality will be the determining factor:
public class SomeClass : IEquatable<SomeClass>
{
public string Name { get; set; }
public double Value { get; set; }
public int[] NumberList { get; set; }
/// <summary>
/// Explicitly implemented IEquatable method.
/// </summary>
public bool IEquatable<SomeClass>.Equals(SomeClass other)
{
return other.Name == Name &&
other.Value == Value &&
other.NumberList == NumberList;
}
public override bool Equals(object obj)
{
var other = obj as SomeClass;
if (other == null)
return false;
return ((IEquatable<SomeClass>)(this)).Equals(other);
}
public override int GetHashCode()
{
// Determine some consistent way of generating a hash code, such as...
return Name.GetHashCode() ^ Value.GetHashCode() ^ NumberList.GetHashCode();
}
}
Just spent the whole day writing an extension method looping through reflecting over properties of an object with various complex bits of logic to deal with different property type and actually got it close to good, then at 16:55 it dawned on me that if you serialize the two object, you simply need compare the two strings ... duh
So here is a simple serializer extension method that even works on Dictionaries
public static class TExtensions
{
public static string Serialize<T>(this T thisT)
{
var serializer = new DataContractSerializer(thisT.GetType());
using (var writer = new StringWriter())
using (var stm = new XmlTextWriter(writer))
{
serializer.WriteObject(stm, thisT);
return writer.ToString();
}
}
}
Now your test can be as simple as
Asset.AreEqual(objA.Serialise(), objB.Serialise())
Haven't done extensive testing yet, but looks promising and more importantly, simple. Either way still a useful method to have in your utility set right ?
The best answer is to implement IEquatable for your class - it may not be the answer you want to hear, but that's the best way to implement value equivalence in .NET.
Another option would be computing a unique hash of all of the members of your class and then doing value comparisons against those, but that's even more work than writing a comparison function ;)
Since these are objects my guess is that you will have to override the Equals method for objects. Otherwise the Equals method will give you ok only if both objects refering to the same object.
I know this is not the answer you want. But since there is little number of properties in your class you can easily override the method.