-1

I inherited some code where Equals was overridden but not ==. I fixed that up, but I noticed more problems. In the calling code objects are constructed using interfaces and then compared and the results are not what one would expect. I've put a complete minimal example below. Briefly, given ISilly i1 = new Silly() and ISilly i2 = new Silly, i1==i2 return false, not true. Is there a way to fix this up and should I? I saw some arguments along the lines of "i1 and i2 are interaces. There could be many classes that derive from ISilly so asking if i1 and i2 doesn't make sense except in a reference equality sense". If that's the answer, my two questions would be:

  1. Why does Equals work so beautifully?
  2. How do I stop the application programmer from calling "i1 == i2"? It more natural for him/her to write that than i1.Equals(i2).

I tried putting in public static bool operator ==(ISilly s1, ISilly s2)- notice the interface instead of class name. I get compiler error.

What am I missing here? I have tried searching for an article that addresses this with no luck. I would have thought it is a common problem.

Please let me know if anything is unclear or I can provide more information. Thanks, Dave

UPDATE !!! I just found this related question: Operator Overloading with Interface-Based Programming in C# As I read it, the answer seems to state that you just can't use == with interfaces and one of the answers suggests using a 3rd party tool such as Resharper to disallow it. Given what nasty bugs it can produce, I question the usefulness of == at all. Why even allow it?

namespace EqualityProblems
{
    class Program
    {
        static void Main(string[] args)
        {
            // just use class, not interface!
            Silly s1 = new Silly();
            Silly s2 = new Silly();
            Silly s3 = new Silly(42);
            Silly s4 = null;
            Silly s5 = null;

            Console.WriteLine("s1.Equals(s2) should be true " + s1.Equals(s2)); // should be true
            Console.WriteLine("s1.Equals(s3) should be false " + s1.Equals(s3)); // should be false
            Console.WriteLine("s1.Equals(s4) should be false " + s1.Equals(s4)); // should be false
            Console.WriteLine("s1 == s2 should be true " + (s1 == s2)); // should be true
            Console.WriteLine("s1 != s2 should be false " + (s1 != s2)); // should be false
            Console.WriteLine("s1 == s3 should be false " + (s1 == s3)); // should be false
            Console.WriteLine("s4 == s1 should be false " + (s4 == s1)); // should be false
            Console.WriteLine("s1 == s4 should be false " + (s1 == s4)); // should be false
            Console.WriteLine("s4 == s5 should be true " + (s4 == s5)); // should be true;both are null

            //Console.WriteLine("s4.Equals(s1) should crash " + s4.Equals(s1)); // should crash. s4 is null

            ISilly i1 = new Silly();
            ISilly i2 = new Silly();
            ISilly i3 = new Silly(42);
            ISilly i4 = null;
            ISilly i5 = null;

            Console.WriteLine("i1.Equals(i2) should be true " + i1.Equals(i2)); // should be true
            Console.WriteLine("i1.Equals(i3) should be false " + i1.Equals(i3)); // should be false
            Console.WriteLine("i1.Equals(i4) should be false " + i1.Equals(i4)); // should be false
            Console.WriteLine("i1 == i2 should be true " + (i1 == i2)); // should be true BUT IS FALSE
            Console.WriteLine("i1 != i2 should be false " + (i1 != i2)); // should be false BUT IS TRUE
            Console.WriteLine("i1 == i3 should be false " + (i1 == i3)); // should be false
            Console.WriteLine("i4 == i1 should be false " + (i4 == i1)); // should be false
            Console.WriteLine("i1 == i4 should be false " + (i1 == i4)); // should be false
            Console.WriteLine("i4 == i5 should be true " + (i4 == i5)); // should be true;both are null

            //Console.WriteLine("i4.Equals(i1) should crash " + i4.Equals(i1)); // should crash. i4 is null

        }
    }

    public interface ISilly
    {
        int Length { get; set; }
    }

    public class Silly : ISilly
    {
        public Silly(int n) { Length = n; }
        public Silly() { Length = 7; }
        public int Length { get; set; }
        public override bool Equals(object obj)
        {
            return obj is ISilly sl && (sl.Length == Length);
        }

        public bool Equals(Silly other)
        {
            if (other == null) return false;
            return Length == other.Length;
        }

        public override int GetHashCode()
        {
            return Length;
        }

        public static bool operator ==(Silly s1, Silly s2)
        {
            if (ReferenceEquals(s1, null))
            {
                return ReferenceEquals(s2, null) ? true : false;
            }
            return s1.Equals(s2);
        }

        public static bool operator !=(Silly fs1, Silly fs2)
        {
            return !fs1.Equals(fs2);
        }


    }
}
12
  • 2
    Equals is a virtual method. I guess this is the reason why it works beautifully. I also doubt that you can stop app programmers from calling ==. Static analysis may help but compiler is is not your friend here. Commented Sep 29, 2019 at 1:24
  • 1
    its part of rosyln @Dave github.com/dotnet/roslyn/wiki/… Commented Sep 30, 2019 at 17:23
  • 1
    At this point, it is not clear what your question is. You have already found the duplicate question for what you originally asked (stackoverflow.com/q/728434). The only question I see left is "I question the usefulness of == at all. Why even allow it?", which seems more like an editorial statement/primarily-opinion-based question. There is in fact a decent argument for not allowing overloading of any operators -- doing so is an easy way to confuse inexperienced coders. But, for many, the convenience outweighs the pitfalls. Do you have an actionable question left here? Commented Sep 30, 2019 at 21:20
  • 1
    There are some code smells here. 1. You're using an interface, so it's safe to assume you've got multiple implementers. Yet you want to check those different classes on value equality? The idea is that value equality can only exist for two instances of the same type, and as such, should be implemented on the concrete type itself. 2. Interfaces are (supposed to be) used for modelling behaviors, you can inject them, forward them, but in the end it's the methods on the interface that need to be called. Instead you're trying to apply overloaded operators on it (ref your referenced post). Commented Oct 1, 2019 at 10:05
  • 1
    Thanks Funk. In this particular case, there is only one interface and one implementation. That's how I got it. I take your points about value equality is only for instances. But how does one discourage or prevent a user of your interfaces from calling "=="? It doesn't matter whether I've implemented an overrride or not; it will be called an return referential equality. If I've provided an "Equals", the user certainly can call i1.Equals(i2) and will get value equality. But of course, I can't force the user of the library to use Equals and not ==. Note folks mentioned analyzers and FxCop Commented Oct 1, 2019 at 14:10

2 Answers 2

1
+50

Don't know if it's possible for you, but if you can use an abstract class instead of an interface, what you could do is implement the == operator as a call on an abstract Equals().

That way anyone using == on a derived class would get the same result as calling Equals.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks foundian1. I'm not sure why somebody welcomed you to StackOverlow by voting your answer down. I voted it back up. I think my question has attracted some nasty trolls. In any case thanks. I played around with your ideas. I can't use them on this existing project but will keep it in mind for the future.
Thanks foudian1. All useful answers and comments, but I can only pick one.
1

operator == and operator != applied on two interface references compare the references as these interfaces have no operators overloading possibilities as these are static methods.

The CLR can't call them.

Duplicate of How do I override the equals operator == for an Interface in C#?

2 Comments

Thank you Olivier - I hadn't seen that question! Unfortunately, it's difficult to stop application programmers from using == rather than .Equals(). I'm checking into Analyzers as suggested by Daniel White in the comments.
You can only tell them that interface are not classes nor structs nor data types... so they not exist directly for calculating... Here is an explaination: What is the difference between an interface and a class

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.