Why aren't type constraints part of the method signature?
Asked Answered
G

2

13

UPDATE: As of C# 7.3, this should no longer be an issue. From the release notes:

When a method group contains some generic methods whose type arguments do not satisfy their constraints, these members are removed from the candidate set.

Pre C# 7.3:

So I read Eric Lippert's 'Constraints are not part of the signature', and now I understand that the spec specifies that type constraints are checked AFTER overload resolution, but I'm still not clear on why this MUST be the case. Below is Eric's example:

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

This doesn't compile because overload resolution for: Foo(new Giraffe()) infers that Foo<Giraffe> is the best overload match but then the type constraints fail and a compile-time error is thrown. In Eric's words:

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.

Type constraints are NOT part of the signature, but why can't they be? What are some scenarios where it is a bad idea to consider type constraints part of the signature? Is it just difficult or impossible to implement? I'm not advocating that if the best chosen overload is for whatever reason impossible to call then silently fallback to the second best; I would hate that. I'm just trying to understand why type constraints can't be used to influence the choosing of the best overload.

I'm imagining that internally in the C# compiler, for overload resolution purposes only (it doesn't permanently rewrite the method), the following:

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

gets transformed to:

static void Foo(Reptile  t) { }

Why can't you sort of "pull in" the type constraints into the formal parameter list? How does this change the signature in any bad way? I feel like it only strengthens the signature. Then Foo<Reptile> will never be considered as an overload candidate.

Edit 2: No wonder my question was so confusing. I didn't properly read Eric's blog and I quoted the wrong example. I've edited in the example I think more appropriate. I've also changed the title to be more specific. This question doesn't seem as simple as I first imagined, perhaps I'm missing some important concept. I'm less sure that this is stackoverflow material, it may be best for this question/discussion to be moved elsewhere.

Goya answered 25/2, 2012 at 3:21 Comment(4)
The bit about Iguanas that you quoted at the top of your question was intended to illustrate a situation where type inference does take a constraint into account, namely, the constraint on T in C<T>, and therefore overload resolution ultimately chooses the non generic method in this example. Are you sure that's the relevant bit of the article that you intend to quote in order to ask this question? The remainder of the question does not appear to follow logically from it.Unmarked
I think rather that you intended to ask why it is that when type inference succeeds but infers a type which violates a constraint on the method type parameter, why overload resolution fails when there is an alternative. I'm finding this question very confusing, but then again, it is a confusing part of the specification.Unmarked
You're right. I misread your blog and used the wrong example. No wonder my question was so confused. I've tried to clarify my question as best as I can; I'm also going to read through some of your other blog posts to see if I can improve my understanding on the subject.Goya
I noticed that Eric's blog post has 9 pages of comments (blogs.msdn.com/b/ericlippert/archive/2009/12/10/…) within which Eric has written numerous responses addressing many of questions like mine. If I get the time, I'm going to try to make a compilation of Eric's relevant responses.Goya
A
6

The C# compiler has to not consider type constraints as part as the method signature because they are not part of the method signature for the CLR. It would be disastrous if the overload resolution worked differently for different languages (mainly due to the dynamic binding that may happen at runtime and should not be different from one language to another, or else all hells would break loose).

Why was it decided that these constraints would not be part of the method signature for the CLR is another question alltogether, and I could only make ill informed suppositions about that. I'll let the people in the know answer that.

Appraise answered 26/2, 2012 at 2:7 Comment(2)
But that just shifts the question from "why isn't it part of C#, to why isn't it part of the CLR"... There is a principal problem with supporting overload resolution that considers type constraints (see my answer).Megdal
There are several features in several .Net languages which are not implemented by the other ones. The reason mostly given is that an auxiliary syntax can be trivially converted to proper syntax. This being one of the very simpler ones, of simply determining which method call to place. Furthermore this would hardly be the only case of possible ambiguity in the language and there is no reason at all for the compiler to not simply use what it finds or give an error if it cannot resolve a specific method call, Just like it gives in dozens of other situations. Language features are not VM features.Sodden
M
1

If T matches multiple constraints, you create an ambiguity that cannot be automatically resolved. For example you have one generic class with the constraint

where T : IFirst

and another with the constraint

where T : ISecond

You now want T to be a class that implements both IFirst and ISecond.

Concrete code example:

public interface IFirst
{
    void F();
}

public interface ISecond
{
    void S();
}

// Should the compiler pick this "overload"?
public class My<T> where T : IFirst
{
}

// Or this one?
public class My<T> where T : ISecond
{
}

public class Foo : IFirst, ISecond
{
    public void Bar()
    {
        My<Foo> myFoo = new My<Foo>();
    }

    public void F() { }
    public void S() { }
}
Megdal answered 25/2, 2012 at 3:28 Comment(3)
..This causes a name collision; in my question the error is a caused by the compiler not knowing which method overload to pick for the method call: Bar(new Iguana(), null). I'm probably being slow, but I don't yet see the connection between these two. I'm going to study your example a bit more..Goya
@EricJ you can also cause method ambiguity without using generics- the same situation would occur if two overloads of a method took IFirst and ISecond respectively, and you tried to resolve which method to call with an argument of type Foo. I don't think that this is why the C# team decided not to make it part of the method signature.Paisano
@ChrisShain Hmm, but if you do have ambiguity for the method call taking IFirst and ISecond the compiler will error out and you can fix the problem by casting the parameter to the interface you want. If generic constraints were part of method signatures you would need a syntax to pick between different constraints at the call site, and any changes to a library's generic constraints would mean calling code would have to be recompiled to continue working which I think is less than ideal.Charmaincharmaine

© 2022 - 2024 — McMap. All rights reserved.