1

I have a problem with operator resolution on generic methods.

From my understanding of section 7.3.4 of the spec within the function EqualOperatorGeneric (sample code below) the correct overload of the == operator on the type A should be found, but instead it seems to get the candidate for (object, object).

Am I doing something very obvious wrong? Is there a method to get the expected behaviour and if not can I turn the given case into a compile time or runtime error?

public class A
{
    public A(int num)
    {
        this.Value = num;
    }

    public int Value { get; private set; }

    public override bool Equals(object obj)
    {
        var other = obj as A;
        if (Object.ReferenceEquals(other, null))
            return false;

        return Object.Equals(this.Value, other.Value);
    }

    public override int GetHashCode()
    {
        return this.Value.GetHashCode();
    }

    public static bool operator ==(A l, A r)
    {
        if (Object.ReferenceEquals(l, null))
        {
            return !Object.ReferenceEquals(r, null);
        }

        return l.Equals(r);
    }

    public static bool operator !=(A l, A r)
    {
        return !(l == r);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(EqualOperatorGeneric(new A(1), new A(1)));
    }

    public static bool EqualOperatorGeneric<L, R>(L l, R r)
        where L : class
        where R : class
    {
        return l == r;
    }
}

Output:

False

2
  • You could easily cut out at least half of that code and still demonstrate the relevant principle, for clarity's sake. Commented Aug 13, 2014 at 16:50
  • I personally like repros to be a little verbose, but i could prolly remove the other equality checks. Commented Aug 13, 2014 at 16:53

2 Answers 2

2

When EqualOperatorGeneric is compiled the == operator needs to be bound statically, when the method is compiled, to a single implementation. It is not bound separately for each separate usage of the generic method.

This is what differentiates generics from, say, C++ templates. The generic method is compiled once and then applied to every usage with every set of type arguments, whereas templates are compiled separately for each set of generic arguments.

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

7 Comments

This makes a lot of sense (was thinking about this more from a C++ templates view) implementation wise. But does it not go directly against the wording of the spec? This is a repro of an incredibly nasty bug I have to deal with. It there any way to detect this scenario at compile time?
@MrDosu No, it doesn't go against the spec. For the purposes of overload resolution, the types of the two operands to the operator are object and object, because when compiling the generic method the types could resolve to any type of object. As for detecting it, what do you mean by that? Detecting the use of operators within generic functions that have operands of a generic type? That doesn't sound all that useful; you'd just see tons of correct usages of general purpose operators.
I guess "Determine the type of T" can be interpreted a multitude of ways and in this instance of compile once generics object is the logical answer. Still pragmatically this means that == operators are fundamentally broken in generics.
@MrDosu No, they're not fundamentally broken, they just work fundamentally differently than you expect. They work exactly as they were designed to work. Your expecting generic operations to be dynamically compiled, and they aren't. Your fundamental understanding of generics is simply wrong.
If they work fundamentally differently in a certain scenario and change the semantics of equality in a clearly defined class I would pragmatically call that broken. Technically you might call that a beautiful feature of the language, you must a agree its very low on the usefulness scale. Semantics.
|
1

After scrounging the spec I realized you can use the dynamic keyword to defer binding of the operator from compile time to runtime. This fixes the issues I have been having:

public static bool EqualOperatorGeneric<L, R>(L l, R r)
{
    dynamic dl = l, dr = r;
    return dl == dr;
}

8 Comments

I would very strongly advise you against doing this. Rather than using a tool specifically designed to be statically bound and deferring the binding of the entire method's compilation until runtime, you should simply use an equality comparison tool that is inherently designed to not be bound when this generic method is compiled, specifically through the use of object.Equals or an IEqualityComparer. In addition to not needing to re-compile the method every time you invoke it, this will continue to meet the expectations of callers of the method as it is more idiomatic of C#.
In the real world the use of comparison tool is not always decided by the actor that defined the equality logic for the objects in question. Dynamic dispatch is EXACTLY the right tool to use here for the case where the correct polymorphic behaviour cannot be determined at compile time. Your arguments of technical "pureness" are meaningless in the context of using programming language as a tool instead of as a toy.
Cases where the objects to be compared themselves do not define equality in a manor that you would hope for is exactly what IEqualityComparer is there for, and why it sounds like exactly the correct tool for this particular method. You can trivially create an equality comparer that uses an object's == operator to perform its equality check, if that is the semantics you want and the object's Equals method has a different implementation. Having said that, it is generally very poor practice for an object's Equals method an == operator to disagree on equality semantics.
You dont seem to understand the initial case. The objects to be compared define equality like required. Static binding CAUSED the semantics of .Equals and == to be different. Dynamic dispatch brings the correct (meaning as implemented by the object designer) behaviour. Your above comment makes no sense.
Assuming that each Type has defined equality sensibly, that is to say the == operator and the Equals method return the same values for the same objects (when the compile time type of the variables is the same as the run time type), then this effectively means that when actually performing an equality check you should use Equals for late binding an == for early binding. You were using the wrong tool on the caller side, == instead of Equals, given that you want to have late binding. Using the early bound equality tool, ==, and compiling the whole method late, is very roundabout.
|

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.