-2

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);
        }


    }
}
Dave
  • 8,095
  • 14
  • 56
  • 99
  • 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. – Optional Option Sep 29 '19 at 01:24
  • you could write your analyzers. – Daniel A. White Sep 30 '19 at 17:07
  • @DanielA.White What are analyzers? Is that a ReSharper thing? I can't guarantee that users of the library will have ReSharper (or any other 3rd party tool). Thanks! – Dave Sep 30 '19 at 17:22
  • 1
    its part of rosyln @Dave https://github.com/dotnet/roslyn/wiki/Getting-Started-Writing-a-Custom-Analyzer-&-Code-Fix – Daniel A. White Sep 30 '19 at 17:23
  • Thanks Daniel. I'll check that out. At first glance, it seems like quite a learning curve to produce something as simple as "Don't allow == comparisons on interfaces". I'm also curious as to why it's allowed in the first place. Surely folks expect a==b to give the same answer as a.Equals(b). And if "==" is provided, I guarantee folks will type that instead of Equals. – Dave Sep 30 '19 at 17:54
  • 1
    At this point, it is not clear what your question is. You have already found the duplicate question for what you originally asked (https://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? – Peter Duniho Sep 30 '19 at 21:20
  • Peter, I have read, but not necessarily understood the referenced question. I am simply inferring from context that there is no way to overload operator== so that it works with interfaces. If that is the case, I'd like to know what should be done? Should operator== even be overloaded? Should the user of the library be discouraged from using == and if so, how? I am still half expecting an answer of the form: "You are wrong, == can be made to work with interfaces" OR, "This is a common problem, and this is the pattern most folks use". Thanks for reading. – Dave Sep 30 '19 at 21:27
  • _"Should operator== even be overloaded? Should the user of the library be discouraged from using == and if so, how?"_ -- these are primarily opinion-based questions. You've already identified Resharper as one tool you could use to prevent using `==` with interface types. Roslyn-based code analysis tools can also be used for that purpose. It is not a common problem, because when overloading `==` is done, it's usually in a context where code is not comparing interface references. It's not really as big of a hazard as one might think. – Peter Duniho Sep 30 '19 at 21:55
  • Thanks for your comment Peter. I must admit. I am surprised. I thought all C# programmers (amongst others) were told to "program to the interface". Thus in my example above, one would use ISilly i1 = new Silly(). If then asked to compare one object to another, the most straightforward way would seem to be "if (i1 == i2) {do this!}" where i1 and i2 are both interfaces. But this would yield a different result than the more verbose i1.Equals(i2). I will look into the Roslyn code analysis tools. I have no idea if the users of the library have ReSharper. Thanks. – Dave Sep 30 '19 at 22:03
  • 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). – Funk Oct 01 '19 at 10:05
  • To sum up, values (or state) are on one side of the equation, behaviors are on the other. – Funk Oct 01 '19 at 10:06
  • 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 – Dave Oct 01 '19 at 14:10

2 Answers2

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#?

  • 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. – Dave Oct 02 '19 at 15:46
  • 1
    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](https://stackoverflow.com/questions/10914802/what-is-the-difference-between-an-interface-and-a-class-and-why-i-should-use-an/58174007#58174007) –  Oct 02 '19 at 15:51
0

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.

foudian1
  • 111
  • 3
  • 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. – Dave Oct 02 '19 at 15:43
  • Thanks foudian1. All useful answers and comments, but I can only pick one. – Dave Oct 06 '19 at 15:05