For user defined value types, your code won't compile.
Specifically, it would fail compilation with the following error: "Operator '==' cannot be applied to operands of type 'a' and 'a' ".
"The == and != operators cannot operate on a struct unless the struct explicitly overloads them."
You have to overload both of them. You most probably do not want to utilize the default Equals()
in your method, since
"...for structs, the default implementation of Object.Equals(Object) (which is the overridden version in System.ValueType) performs a value equality check by using reflection to compare the values of every field in the type. When an implementer overrides the virtual Equals method in a stuct, the purpose is to provide a more efficient means of performing the value equality check and optionally to base the comparison on some subset of the struct's field or properties."
For user defined reference types (simplified case, as in the OP's example):
"The == and != operators can be used with classes even if the class does not overload them. However, the default behavior is to perform a reference equality check. In a class, if you overload the Equals method, you should overload the == and != operators, but it is not required."
If you do not overload the operators, there will most probably only be a reference equality test.
"Simplified case", because operator overload resolution might select another implementation instead of the default.
//Minimal example, for demonstration only.
//No Equals(), GetHaschode() overload, no IEquatable<T>, null checks, etc..
class Program
{
static void Main()
{
MyMoreDerived a = new MyMoreDerived() { fbase = 1, fderived = 3 };
MyMoreDerived b = new MyMoreDerived() { fbase = 2, fderived = 3 };
//Even though MyMoreDerived does not overload the operators, this
//will succeed - the definition in MyDerived will be used.
if (a == b)
{
//Reached, because the operator in MyDerived is used.
Console.WriteLine("MyDerived operator used: a == b");
}
a.fderived = 2;
b.fbase = 1;
//a => {1, 2}
//b => {1, 3}
//Since 2 != 3, the operator in MyDerived would return false.
//However only the operator in MyBase will be used.
if ((MyBase)a == (MyBase)b)
{
//Reached, because the operator in MyBase is used.
Console.WriteLine("MyBase operator used: a == b");
}
b.fderived = 2;
//a => {1, 2}
//b => {1, 2}
//Now both operator definitions would compare equal,
//however they are not used.
if ((object)a != (object)b)
{
//Reached, because the default implementation is used
//and the references are not equal.
Console.WriteLine("Default operator used: a != b");
}
}
class MyBase
{
public int fbase;
public static bool operator ==(MyBase x, MyBase y)
{
return x.fbase == y.fbase;
}
public static bool operator !=(MyBase x, MyBase y)
{
return x.fbase != y.fbase;
}
}
class MyDerived : MyBase
{
public int fderived;
public static bool operator ==(MyDerived x, MyDerived y)
{
return x.fderived == y.fderived;
}
public static bool operator !=(MyDerived x, MyDerived y)
{
return x.fderived != y.fderived;
}
}
class MyMoreDerived : MyDerived
{
}
}
Singletons are most meaningful in the context of reference types and their purpose is to return one specific instance. I cannot imagine a reasonable case where the reference is the same but the object is not "equal" to itself.
Even with remoting it is best practice to separate operation contracts from data contracts.
The former will typically be implemented by MarshalByRefObject
s on the server side - implementing operations defined by interfaces - while the latter with data/message classes that are marshaled by value and might be shared by the client and server. It might not be a big problem if you overload the operators in data classes. These, however should not reference/make calls to remote objects, I believe.
Even if you provide a custom client proxy that overloads the operators, imho it is a really bad practice and a debugging nightmare to hide remoting calls behind ==
and !=
operators.
(If I understand your intentions, that I am not sure of.)