4

Possible Duplicate:
Generic methods and method overloading

Ok, I hit this one by accident... Giving this situation:

class Program {
    static void Main( string[ ] args ) {

        var obj = new gen<int>( );
        Console.Write( obj[ 1 ] );
        Console.ReadKey( );

    }
}

class gen<T> {

    public int this[ T i ] { get { return 2; } }

    public int this[ int i ] { get { return 1; } }

}

It will always print 1. I would have expected for the compiler to complain, or the runtime to crash and burn and melt the CPU, but no, it is happy to print '1'

Of course I can make a choice to return either if I use any other type for the generic parameter. For giggles, I try using UInt as the generic type parmater, and I can differentiate between the calls, so the questions are:

  1. Why C# does not freak out? Shouldn't Anders Hejlsberg feel a disturbance in the force?

  2. How can I restrict a generic parameter from certain types? As in this T can be anything but ints (but long are OK)

Community
  • 1
  • 1
JorgeLeo
  • 115
  • 9
  • asking for a constraint like `where T : anything-but-int` has been asked and answered on SO (eg [here](http://stackoverflow.com/questions/8727523/generic-not-constraint-where-t-ienumerable) ) (short answer: you can't) – AakashM Oct 23 '12 at 14:33
  • 1
    [Here's the answer](http://stackoverflow.com/a/3680367/142637) – sloth Oct 23 '12 at 14:33
  • I did a quick search and neither popup... but thank you for the pointers – JorgeLeo Oct 23 '12 at 14:57

4 Answers4

9

I believe this is specified in section 7.5.3.2 of the C# 4 spec (Better Function Member).

  • Otherwise, if MP has more specific parameter types than MQ, then MP is better than MQ [...]
    • A type parameter is less specific than a nontype parameter
    • [...]

So here, the member with the T parameter is less specific than the member with the int parameter.

Try to design your way out of this one simply by not overloading like this. It's hard for indexers of course, but you could always provide methods instead (or possibly as well, as a fall-back).

EDIT: Note that if you have overloaded methods where both are type parameters, the compiler will complain:

public class Foo<T1, T2>
{
    public void Bar(T1 t1) {}
    public void Bar(T2 t2) {}
}

...

Foo<int, int> foo = new Foo<int, int>();
foo.Bar(10); // Error

Here neither method is more specific.

How can I restrict a generic parameter from certain types? As in this T can be anything but ints (but long are OK)

This is really an entirely separate question, but basically you can't. You can constrain type parameters in various ways, but not by explicitly including and excluding types. See the MSDN page on constraints for more information.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    Damn, I just started reading this question and my first thought was, I bet the Skeet man is gonna know this one of the top of his head – Mikey Mouse Oct 23 '12 at 14:33
  • @MikeyMouse: I hate to disappoint you, but I really did have to get the spec out... – Jon Skeet Oct 23 '12 at 14:41
  • So bottom line... it is part of the specs that it will use the more explicit version over the implicit version... is ok, to me it sounds a bit to close to "is not a bug, is a feature", but I'll take it (I don't have any other option) – JorgeLeo Oct 23 '12 at 14:59
  • @JorgeLeo: I'm sure the C# team gave it thorough consideration - I strongly suspect that any alternative design you come up with would have *other* disagreeable edge cases. – Jon Skeet Oct 23 '12 at 15:00
  • And the answer for the second question is that there is no need to add anything to the language so the developer can say what to favor in the call because the the problem was resolved in the spec. – JorgeLeo Oct 23 '12 at 15:01
  • @JonSkeet Yes, I do think so too... and I do think that this kinds of trade offs was part of the reasons that generics waited until c# 2.0 instead being there from the get go. I still prefer to have generics and work around these kind of edge cases, than not to have generics at all – JorgeLeo Oct 23 '12 at 15:03
8

As Eric Lippert says:

The C# specification says that when you have a choice between calling ReallyDoIt(string) and ReallyDoIt(string) – that is, when the choice is between two methods that have identical signatures, but one gets that signature via generic substitution – then we pick the “natural” signature over the “substituted” signature.

Also this process described in C# spec 7.5.3.2 (Better function member):

In case the parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN} are equivalent (i.e. each Pi has an identity conversion to the corresponding Qi), the following tie-breaking rules are applied, in order, to determine the better function member.

  • If MP is a non-generic method and MQ is a generic method, then MP is better than MQ (as John pointed, this is true when you have generic method, not generic type)
  • ...
  • Otherwise, if MP has more specific parameter types than MQ, then MP is better than MQ. Let {R1, R2, …, RN} and {S1, S2, …, SN} represent the uninstantiated and unexpanded parameter types of MP and MQ. MP’s parameter types are more specific than MQ’s if, for each parameter, RX is not less specific than SX, and, for at least one parameter, RX is more specific than SX:

    • A type parameter is less specific than a non-type parameter (this is your case - thus methods are not generic, and inferred parameter type equals to non-generic parameter type)
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
0

The C# compiler always chooses the more-specific versus the more-generic method if the call could fit in both =). That's why it doesn't freak out, he just follows his rules.

Gaspa79
  • 5,488
  • 4
  • 40
  • 63
-2

C# compiler doesn't freak out because both method are valid, and both can be called.

Here's an example that return "2":

Gen<Form> gen = new Gen<Form>();
textBox1.Text = gen[this].ToString();

Where "this" is a form. Of course, using an index accessor as an object instead of a number... Well, whatever, it works.

But like everybody else said, the compiler will prefer the explicit over the implicit.

LightStriker
  • 19,738
  • 3
  • 23
  • 27
  • It's not up to the JIT - it's up to the C# compiler. Also, just because in *some* cases you can call it doesn't necessarily mean that it would be valid in *this* case. In a situation where you have two type parameters and overloaded members which take each of them, the C# compiler *will* complain - and yet your "you can call it if you do something different" argument would still be available... – Jon Skeet Oct 23 '12 at 14:42
  • Fine, compiler. My mistake. "some case" vs "this case" is pointless as without constraint the compiler has no way to know what T is. From my point of view, T could be a potato, and both method are quite valid and both can be called independently. – LightStriker Oct 23 '12 at 14:47
  • 1
    No, it's not pointless at all. The OP wasn't asking why the compiler didn't complain *in his specific case*. Your code doesn't prove anything. I don't believe the OP was asking why `gen` compiled - he was asking why his way of *calling the indexer* in `gen` compiled. – Jon Skeet Oct 23 '12 at 14:51
  • Ok... other way around, why it would NOT compile? He passes 1 to a method, which one of the method overloads has an explicit definition for this parameter. Compiling and which method to use is two different things. – LightStriker Oct 23 '12 at 15:14
  • See my answer for an example of where it wouldn't compile - where *both* are generic type parameters. The language *could* be defined so that a type parameter declared earlier in the list of type parameters was more specific than a type parameter declared later... but it isn't. Basically the language can be defined in many ways here. – Jon Skeet Oct 23 '12 at 15:16