Constraints are not part of the signature

Constraints are not part of the signature

Rate This

What happens here?

class Animal { }
class Mammal : Animal { }
class Giraffe : Mammal { }
class Reptile : Animal { }

static void Foo<T>(T t) where T : Reptile { }
static void Foo(Animal animal) { }
static void Main()
{
    Foo(new Giraffe());
}

Most people assume that overload resolution will choose the second overload. In fact, this program produces a compile error saying that T cannot be Giraffe. Is this a compiler bug?

No, this behaviour is correct according to the spec. First we attempt to determine the candidate set. Clearly the second overload is a member of the candidate set. The first overload is a member of the candidate set if type inference succeeds.

The method type inference algorithm considers only whether the method type arguments can be consistently inferred from the types of the arguments. Method type inference cares not a bit about whether the resulting method is malformed in some other way. Its only job is to work out the best possible type arguments given the arguments to the method. Clearly the best type for T is Giraffe, so that’s what we infer.

So we now have two methods in the candidate set, Foo<Giraffe>, and the second overload. Which is better?

Again, overload resolution looks only at the arguments you passed in, and compares them to the types of all the candidates. The argument is of type Giraffe. We have a choice: argument of type Giraffe goes to parameter of type Giraffe, or argument of type Giraffe goes to parameter of type Animal. Clearly the former is better; it’s an exact match.

Therefore we discard the second overload because it is worse than another candidate. That leaves one candidate left, which is an exact match. Only then, after overload resolution, do we check to see whether the generic constraints are violated.

When I try to explain this to people, they often bring up this portion of the specification as evidence that I am wrong, wrong, wrong:

If F is generic and M has no type argument list, F is a candidate when:
1) type inference succeeds, and
2) once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of F satisfy their constraints, and the parameter list of F is applicable with respect to A. [emphasis added]

This appears at first glance to say that Foo<Giraffe> cannot be a candidate because the constraints are not satisfied. That is a misreading of the specification; the bit “in the parameter list” is referring to the formal parameter list, not the type parameter list.

Let me give you an example of where this rule comes into play, so that it’s clear. Suppose we have

class C<T> where T : Mammal {}

static void Bar<T>(T t, C<T> c) where T : Mammal {}
static void Bar(Animal animal, string s) { }

Bar(new Iguana(), null);

Type inference infers that Bar<Iguana> might be a candidate. But that would mean that we are calling a method that converts null to C<Iguana>, which violates the constraint in the declaration of C<T>. Therefore the results of type inference are discarded, and Bar<Iguana> is not added to the candidate set.

So if that’s not the relevant bit of the spec, what is? The relevant bit happens after the best method has been determined, not before.

If the best method is a generic method, the type arguments (supplied or inferred) are checked against the constraints declared on the generic method. If any type argument does not satisfy the corresponding constraints on the type parameter, a compile-time error occurs.

It is often surprising to people that an invalid method can be chosen as the best method, chosen over a less good method that would be valid. (*) The principle here is overload resolution (and method type inference) find the best possible match between a list of arguments and each candidate method’s list of formal parameters. That is, they look at the signature of the candidate method. If the best possible match between the arguments and the signature of the method identify a method that is for whatever reason not possible to call, then you need to choose your arguments more carefully so that the bad thing is no longer the best match. We figure that you want to be told that there's a problem, rather than silently falling back to a less-good choice.

UPDATE: My Twilight-reading friend Jen from a couple episodes back points out that this is not just good language design, it's good dating advice. If the best possible match between your criteria and an available guy on Match.com identifies a guy who, for whatever reason, is impossible to call, then you need to choose your criteria more carefully so that the bad guy is no longer the best match.  Words to live by.


(*) I already discussed the related situation of how it is the case that we can choose a static method over an instance method even when there’s no hope of the static method being the right one.

  • It seems to me that this is indeed a compiler bug, and what you have shown is that it results from a bug in the spec.

    No, a bug in the compiler would be an implementation that doesn't match the spec. A bug in the spec is a spec which doesn't match the desires of the language designers. In this case the implementation, the spec, and the language designers all agree. Now, you might be of the opinion that this was a lousy design decision, but it is not an error. -- Eric 

    What is gained by discarding constraint information when finding the best overload?

    The constraint information is not discarded; it's never even considered in the first place.

    What is gained is the resulting error analysis: if the best applicable match from argument types to formal parameter types produces an error then you probably have an error in your program. We assume that you intended the compiler to choose the best applicable match; if doing so violates a constraint, then we assume you want to be told about that. We don't say "well, the choice with the best match is A, but since that's not going to work out, let's silently choose B and not tell anyone about it."

    C# tries to not be a "hide your errors and muddle on through" sort of language.

    -- Eric

  • This method of overload resolution prevents us from doing something like this

           static void Foo<T>(T t) where T : Reptile { }
           static void Foo<T>(T t) where T : Mammal { }

    I can't think of anytime I would necessarily want to do it, perhaps there's something involving the cold-blooded nature of reptiles that you couldn't possibly achieve in a less-constrained generic method, but you're prevented nonetheless. You get "Type blah already defines a member called 'Foo' with the same parameter types."

    This is a consequence of the title of the post: neither the CLR nor C# considers constraints to be part of the signature of a method. Methods of the same name must differ in signature. -- Eric

  • @Jonathan

    I do not see this as a bug at all. If you cannot overload a method through constraints then it only stands to reason that the compiler should ignore them when performing overload resolution. Any other behavior would be unexpected and not intuitive.

    An analog case of what you are asking the compiler to do is to perform overload resolution based on return types when you cannot overload methods that way:

      static Reptile Foo(Reptile  reptile) {…}

      static Mammal Foo(Mammal mammal) {…}

      ...

      Mammal m = Foo(null); //Compile time error. Ambiguous call

    Which is the best match? Well logically it would seem Foo(Mammal mammal) should be the choice because it returns Mammal but due to the fact that overload resolution doesn't consider return types we get a compile time error.

    Is this a bug? No, the compiler is doing what it should do.

    The golden rule in my opinion should always be that if the compiler has to “guess” in anyway what the code is trying to do then it is better to throw a compile time error than to guess wrong.

  • Sounds like this is a defect in the C# specification. In Visual Basic it honors constraints for the purpose of overload resolution and this compiles just fine.

    No, this is not a defect in either the specification or the design of the language. This is a deliberate design choice in accordance with the long-standing principles of the design of the C# language. To do it the other way would be a defect.

    Reasoning about C# design choices by pointing out that VB does it differently is not compelling. The VB language designers are building a different language and are making different design choices, in accordance with the design principles for their language. C# has always been a "complain loudly if something looks potentially incorrect" language, and VB has always been a "do your best to figure out what the user meant even if it means sometimes guessing wrong" language. Both philosophies are sensible and useful, and we offer you the choice.

    For example, VB allows overload resolution to succeed when you pass an expression of type Giraffe to a method that requires a parameter of type Mammal, silently inserts a typecast, and crashes at runtime if that turns out to be a bad choice. Is it your contention that this is also a sensible rule for C#, just because it is a sensible rule for VB?

    -- Eric

  • So much technicality, so little discernment. The code at the beginning of this blog entry should be the only evidence required to show that the the compiler should be changed, whether or not the spec would need to be changed first. And by the way, if Match.com determined that the best match for me was someone who is not even available on Match.com, I would hope that this would not cause the website to display an error message.

    I'm not sure how many more ways I can come up with to explain this. The correct behaviour here is to give an error. The best possible choice is not viable; the correct behaviour is to tell you that, rather than blithely choose the second-best choice and hope for the best. -- Eric

  • > The golden rule in my opinion should always be that if the compiler has to “guess” in anyway what the code is trying to do then it is better to throw a compile time error than to guess wrong.

    Where is the guessing? There is only one viable overload at the call site, the generic version shouldn't be considered at all.

    The generic version is the best possible choice, and still, it doesn't work. You don't think that's a sensible candidate for an error message? -- Eric

    > An analog case of what you are asking the compiler to do is to perform overload resolution based on return types when you cannot overload methods that way:

    That is not the same thing because both Foo(Reptile) and Foo(Mammal) are viable canidates for the parameter null.  In the first example, there is only one viable canidate for Foo(Giraffe).

    As further proof, it returns an "ambiguous call" error not a a generic type parameter error.

  • Also a Giraffe is not a Reptile, end of story!

  • > The generic version is the best possible choice, and still, it doesn't work. You don't think that's a sensible candidate for an error message? -- Eric

    What would we lose by considering the type constraints for the purpose of choosing which choice is best?

    I would think that the answer is obvious. When you make an error case into a legal case then you lose the ability to detect the error. (I am for some reason reminded of all those US congresspeople who, over the years implemented much "deregulation" and yet seemed to not realize that "deregulation" simply means "taking acts that used to be crimes against the American people and making them legal". As the worldwide financial crisis has shown, perhaps some of those acts ought to have remained crimes.) -- Eric

    If this is like the ambiguous case caused by the Color Color problem, show us an example.

     

  • The compiler would not be guessing at what I want, nor would it be settling for the second best anything. Just think about the inheritance. Here is some simple logic:

    There are two methods called Foo.

    No, there is one non-generic method called foo, and there is a pattern for generating infinitely many additional methods called Foo, the pattern is called Foo<T>. -- Eric

    I am calling a method called Foo.

    One of those methods expects a Reptile.

    No, not at all. One of the infinitely many additional methods called foo expects a Reptile. One of them expects a Giraffe. One of them expects an int. One of them expects any (non-pointer, non-void) type you care to name. -- Eric

    The other expects an Animal.

    I am passing a Giraffe.

    A Giraffe is never a Reptile.

    A Giraffe is always an Animal.

    It should call the method that accepts what I am passing, a Giraffe, which is an Animal.

    No, it should tell you that you are attempting to call the one that takes a Giraffe, and that it is not legal to do so. -- Eric

    I should not have to do anything special to make it clear which method I intend to call, because there is only one method that I could be calling. If you get too far into the technicalities of the specification and how generic parameters work, you will lose perspective. The correct perspective can be gained by looking at the code at the beginning of this blog entry. It is good to think of things from the perspective of the user, which in this case is a C# programmer.

    I am. We hear over and over from our users that the desired behaviour of the language is to inform the developer when the code is wrong, rather than making the guess "Oh, I suppose the developer actually didn't mean me to call the exact match, they must have meant to call something else." When faced with incorrect code, we do not make an attempt to guess what it is that you actually meant, we tell you that the code is incorrect. -- Eric

    I agree that whenever compiler has to guess at what I want, it should display an error, but there is no guessing here just because the two methods are similar. This is just treating the programmer like they don't know what they are doing. Should VS also display message prompts asking if I am sure that I want to call Foo and list all methods with a name that is spelled similarly?

    I'm not following your line of argument here. I would think that the more germane analogy is that you want to call RetrieveRecord(), and you type in RetreiveRecord() by mistake. We do not then say "well, the developer probably meant RetrieveRecord, let's call that instead." We tell you that there's a mistake and let you figure out how to fix it. That is treating the programmer like they know what they're doing. When the compiler makes assumptions about what you meant in the face of an error, that is when the language treats you like it knows better than you do what you meant. -- Eric

    If I told Match.com that I wanted to date a Giraffe, it better not show an error message saying that a Giraffe is really similar to a Reptile because they are both Animals, and I need to explicitly specify that I don't want do date a Reptile before it will select a Giraffe for me.

    That was just a silly joke. It's not intended to actually be an analogy of the overload resolution process. -- Eric

  • Although I am normally very much in favor of failing fast and raising an error instead of allowing unintuitive behavior, that argument fails to win me over in this case. Not because I think it is invalid, but because I don't think that the alternative behavior is in any way unintuitive or indicative of an error. Eric, you have stated (repeatedly) that the reason why the compiler raises an error in this case is that "The generic version is the best possible choice, and still, it doesn't work." If that argument holds, then of course an error is preferred. But that argument only holds IF you don't consider type parameter constraints in your definition of "best possible choice". The C# spec does not consider them, which accurately reflects the intention of the designers, so this does not qualify as a bug. However, there is no reason that I can see why it shouldn't consider type parameter constraints. If you show me the first code sample and ask me "Which overload is the best choice for this call site?", I will immediately reply that the second overload is the best choice. You haven't presented any evidence that it is better for the compiler to not come to the same conclusion.

    "What would we lose by considering the type constraints for the purpose of choosing which choice is best?

    I would think that the answer is obvious. When you make an error case into a legal case then you lose the ability to detect the error."

    Again, this only holds if you consider the case to be an error. Jonathan clearly doesn't consider it an error, nor do I, so it is not surprising that neither of us understand what is to be gained by not considering the type constraints.

    Apparently I am not explaining this very well. Let me try another tack.

    When you write a call site without generic type arguments, and there is a generic method in the method group, what you are saying is "I request the compiler to infer what generic method I might be attempting to call here, and then, if that method turns out to be the best method, to act as though I actually did call that method." If you typed Foo<Giraffe>(new Giraffe()), you'd quite rightly get an error. When you ask the compiler to work out T for you, the compiler does exactly what you asked. It works out that the best possible value for T is Giraffe, reasoning solely from the arguments that *you* provided and the types provided in the generic version of the method. And if doing so produces a method that has all legal parameter types under construction, then that becomes a candidate, exactly as if you'd typed Foo<Giraffe> explicitly. That method undoubtedly must win, since it is an exact match, and is then discovered to be illegal. You told the compiler to infer what types you meant, it did so, and the result was incorrect. The right behaviour is to say "huh? why'd you ask me to make an inference that you could have known was going to be bogus?" The compiler cannot know what you intended, and rather than guessing, tells you that there's a problem.

    Is that more convincing? -- Eric

     

  • Eric,

    You are thinking too much about the technicalities of how generics work under the covers instead of the way that they are used to achieve a goal.

    You said:

    "No, there is one non-generic method called foo, and there is a pattern for generating infinitely many additional methods called Foo, the pattern is called Foo<T>."

    This is technically true, but the way the C# programmer thinks of this is as 2 methods. The whole point of generics is to allow the programmer to program generically, not to generate methods. The fact that it actually generates methods is a technicality as to how it achieves that goal. Generics allow the programmer to think of this example as only 2 methods.

    I think the issue can be simplified by taking generics out of the equation altogether. Look at this code:

    class Animal { }

    class Mammal : Animal { }

    class Giraffe : Mammal { }

    class Reptile : Animal { }

    static void Foo(Reptile reptile) { }

    static void Foo(Animal animal) { }

    static void Main()

    {

       Foo(new Giraffe());

    }

    Clearly, the second method should be called.

    You also said:

    "We hear over and over from our users that the desired behaviour of the language is to inform the developer when the code is wrong, rather than making the guess "Oh, I suppose the developer actually didn't mean me to call the exact match, they must have meant to call something else.""

    I too desire this from C#. I believe that this is evidence for my case, not against it. From my perspective, considering the first method to be an exact match is a mistake, the second method is a closer match. Instead of requiring the programmer to specify that they meant the second method, they should only have to specify something if they meant the first method, since this is less of a match, at least from my perspective.

  • What if I did this:

    class Animal { }

    class Mammal : Animal { }

    class Giraffe : Mammal { }

    class Reptile : Animal { }

    class Lizard : Reptile { }

    class Iguana : Lizard { }

    static void Foo(Lizard lizard) { }

    static void Foo<T>(T t) where T : Reptile { }

    static void Foo(Animal animal) { }

    static void Main()

    {

       Foo(new Iguana());

    }

    Intuitively, I would expect that Lizard matches better than Reptile and Animal, so Foo(new Iguana()) would call Foo(Lizard lizard). But what actually happens is Lizard is compared to Iguana, not Reptile, because that is the Type inferred for T, so Foo<T>(T t) is called. The compiler just silently did something I did not expect.

  • > Most people assume that overload resolution will choose the second overload. In fact, this program produces a compile error saying that T cannot be Giraffe. Is this a compiler bug?

    > I'm not sure how many more ways I can come up with to explain this. The correct behaviour here is to give an error. The best possible choice is not viable; the correct behaviour is to tell you that, rather than blithely choose the second-best choice and hope for the best. -- Eric

    This is clearly not a compiler bug, and yes, the best choice here is to give an error. But I think this is a spec "bug", because the "best possible choice" of the spec is not "best possible choice" that most (if not all) users expect.

  • "Intuitively, I would expect that Lizard matches better than Reptile and Animal, so Foo(new Iguana()) would call Foo(Lizard lizard). But what actually happens is Lizard is compared to Iguana, not Reptile, because that is the Type inferred for T, so Foo<T>(T t) is called. The compiler just silently did something I did not expect."

    I can see the argument on constraints and signatures (but can live with the current rules), but not this argument. You've got a generic that by rule matches Iguana exactly versus a method that is merely assignment compatible. Even in a pretend world where constraints matter for overload resolution, the generic is the exact and best fit.

  • The people that think the current behavior is wrong are not fully understanding the reasoning behind the C# design team's decision I think. As far as I understand it, its all about avoiding subtle bugs in which the expected generic method will not be called. All the ones who think this is the wrong behavior is because they are expecting (in this trivial example) that the method overload resolution will pick the non generic method because its the only obvious viable option. That seems reasonable, but in a real world scenario with a much more convoluted code base this might not always be the case and maybe, just maybe, the generic method does seem to be the reasonable and expected choice even if its really not viable. If coders were perfect there wouldn't be any bugs and we would all be coding happily in C++. Fact is we all make mistakes, so avoiding these kind of subtle traps in a language is the way to go IMHO.

    @Jonathan

    "That is not the same thing because both Foo(Reptile) and Foo(Mammal) are viable canidates for the parameter null.  In the first example, there is only one viable canidate for Foo(Giraffe)."

    I think you missed the point of the analogy. Be it that both are viable or not is not the point. Its about using return types in method overload resolution. The compiler has enough information to infer what method you want to call if the C# design team had decided to use return types as part of method overload resolution mechanism. You don't consider the fact that it doesn't a bug. Why? I personally find it the right decision because it can be the origin of a whole array of subtle bugs while the advantages of having that feature in the language are very small.

    On the same line of thought I do not see that having constraints as part of the method overload resolution mechanism is a must have feature or something that really makes one's life easier. Quite the contrary I see it like a potential well of subtle bugs as I mentioned above. The compile time error is a way to avoid this and the workaround is trivial. If you really want the non generic method to be called then simply match its signature, and cast Giraffe to Animal.

    And last but not least, the argument that the compiler is not trusting the coder or thinking it knows better does not stand. If you follow that line of thought then the following code should be legal:

    void Blah(int n) {...}

    double d;

    ...

    Blah(d);

    Its obvious I'm calling Blah(int n) so its obvious I know what I'm doing and therefore the compiler should cast d to int silently under the hood and be done with.

    Sorry if I'm not altogether clear, english is not my native language.

Page 1 of 9 (122 items) 12345»