37

The code is a little weird, so bear with me (keep in mind this scenario did come up in production code).

Say I've got this interface structure:

public interface IBase {  }
public interface IChild : IBase {  }

public interface IFoo<out T> where T : IBase {  }

With this extension method class built around the interfaces:

public static class FooExt
{
    public static void DoSomething<TFoo>(this TFoo foo)
        where TFoo : IFoo<IChild>
    {
        IFoo<IChild> bar = foo;

        //foo.DoSomethingElse();    // Doesn't compile -- why not?
        bar.DoSomethingElse();      // OK
        DoSomethingElse(foo);       // Also OK!
    }

    public static void DoSomethingElse(this IFoo<IBase> foo)
    {
    }
}

Why doesn't the commented-out line in DoSomething compile? The compiler is perfectly happy to let me assign foo to bar, which is of the same type as the generic constraint, and call the extension method on that instead. It's also no problem to call the extension method without the extension method syntax.

Can anyone confirm if this is a bug or expected behaviour?

Thanks!

Just for reference, here's the compile error (types abridged for legibility):

'TFoo' does not contain a definition for 'DoSomethingElse' and the best extension method overload 'DoSomethingElse(IFoo)' has some invalid arguments

Cameron
  • 96,106
  • 25
  • 196
  • 225
  • Which version of the .Net Framework are you using? – Jackson Pope May 17 '11 at 17:45
  • 1
    @Jackson: .NET 4 (otherwise I believe `out` wouldn't work) – Cameron May 17 '11 at 17:46
  • @Jackson Pope, isn't that obvious with the `out` covariant generic definition of the `IFoo` interface? – Darin Dimitrov May 17 '11 at 17:46
  • Shouldn't it be `DoSomethingElse(foo)` and `DoSomethingElse(bar)`? The way you have it, foo and bar don't have any member functions – riwalk May 17 '11 at 17:49
  • I believe it's because you are declaring the generic parameter as `out T` instead of `in T`. The IChild cannot be cast into `IFoo`. – Tejs May 17 '11 at 17:49
  • @Stargazer712 No, these are [extension methods](http://msdn.microsoft.com/en-us/library/bb383977.aspx). – Adam Lear May 17 '11 at 17:51
  • @Stargazer: Aha! DoSomethingElse(foo) compiles (though why that would but not the other way, I don't know)! And yes, here `Foo` is absolutely useless, but it's just an example. In my real code, `Foo` has stuff in it. – Cameron May 17 '11 at 17:53
  • @Tejs: I don't think so. The parameter has to be `out` otherwise I wouldn't be able to pass `IFoo`s to `IFoo` parameters (only the other way around). – Cameron May 17 '11 at 17:55
  • @Anna Lear, ahh. Missed that :) – riwalk May 17 '11 at 18:23
  • What's the reason for declaring `DoSomething(this TFoo foo) where TFoo : IFoo` instead of `DoSomething(this IFoo foo)` which is effectively equivalent? – FacticiusVir Jun 09 '11 at 12:51
  • @FacticiusVir: Good question! As you've pointed out, I don't really need the `TFoo` type here. In some of my real code, though, I have additional (interface) constraints on `IFoo`. – Cameron Jun 09 '11 at 13:20

7 Answers7

9

Quoting the C# specification:

7.6.5.2 Extension method invocations

In a method invocation (§7.5.5.1) of one of the forms

expr . identifier ( )

expr . identifier ( args )

expr . identifier < typeargs > ( )

expr . identifier < typeargs > ( args )

if the normal processing of the invocation finds no applicable methods, an attempt is made to process the construct as an extension method invocation. If expr or any of the args has compile-time type dynamic, extension methods will not apply.

The objective is to find the best type-name C, so that the corresponding static method invocation can take place:

C . identifier ( expr )

C . identifier ( expr , args )

C . identifier < typeargs > ( expr )

C . identifier < typeargs > ( expr , args )

An extension method Ci.Mj is eligible if:

· Ci is a non-generic, non-nested class

· The name of Mj is identifier

· Mj is accessible and applicable when applied to the arguments as a static method as shown above

· An implicit identity, reference or boxing conversion exists from expr to the type of the first parameter of Mj.

Since DoSomethingElse(foo) compiles but foo.DoSomethingElse() doesn't, it seems like a compiler bug in overload resolution for extension methods: an implicit reference conversion exists from foo to IFoo<IBase>.

Julien Lebosquain
  • 40,639
  • 8
  • 105
  • 117
  • Interesting. Doesn't `DoSomethingElse(this IFoo foo)` break the non-generic stipulation of Ci? The thing is, I can call that as an extension method on an `IFoo`, just not on a generic type which happens to be `IFoo` (specified with a generic constraint). – Cameron May 17 '11 at 18:41
  • 1
    *Ci* refers to the class containing the extension method, `FooExt` in your sample code: this clauses basically says that you can't put an extension method in a generic or nested class. The only condition for the parameter is the last sentence, which passes the test. – Julien Lebosquain May 17 '11 at 18:52
  • Aha, thanks. That makes more sense. +1 for quoting the spec, though I still don't know why it isn't working ;-) I'm disinclined to believe it's a compiler bug -- they're way too rare. On the other hand, the behaviour I'm seeing doesn't seem to match the spec... – Cameron May 17 '11 at 18:59
5

Can you define DoSomethingElse in the IFoo?

public interface IFoo<out T> where T : IBase
{
    void DoSomethingElse();
}

UPDATE

Maybe you can then change the signature

public static void DoSomethingElse(this IFoo<IBase> foo)
=>
public static void DoSomethingElse<TFoo>(this TFoo foo) 
    where TFoo : IFoo<IChild>
oleksii
  • 35,458
  • 16
  • 93
  • 163
  • Unfortunately, no; not all `IFoo`s will have this method (in my real code I have additional generic constraints on the `TFoo` type parameter in the extension method) – Cameron May 17 '11 at 17:57
  • +1 with the update. Add in the constraint `where TFoo : IFoo` as on the other extension method, and I think you'll be in business. – JSBձոգչ May 17 '11 at 18:05
  • I like what you propose in your update, but in my case it's quite painful to use with extension methods that already have (non-inferred) type parameters, since my real-life IFoo is also templated. I end up with calls like `thing.Method>();` instead of `thing.Method()`; – Cameron May 17 '11 at 18:10
  • I can't shed any light on the problem without diving into the C# spec for a while, but at least for this example, I would simply change the signature of DoSomething like so: `public static void DoSomething(this IFoo foo) { ... }` Does the generic parameter actually get you anything here? – Mike Strobel May 17 '11 at 21:55
  • @Mike: Thanks for the suggestion, but I am actually using the generic out parameter -- I really do need to be able to call `DoSomethingElse` from `IFoo`s as well. – Cameron May 18 '11 at 13:27
  • @Mike: I completely misunderstood your comment the first time, sorry. I thought you were talking about the `IFoo` type parameter, but of course you meant the constraint on the method. I actually *am* using the generic parameter, so that I can specify additional constraints on `TFoo` besides the `IFoo` one. – Cameron Jun 09 '11 at 23:40
3

I have found evidence that this is a "bug".

Although it is not necessary that a CLR language support all features available in MSIL, The fact is what you're trying to do is valid in MSIL.

If you were of a mind to dump the code into IL and make the DoSomething method look like this:

.method public hidebysig static void  DoSomething<(class TestLib.IFoo`1<class TestLib.IChild>) T>(!!T foo) cil managed
{
  .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       52 (0x34)
  .maxstack  1
  .locals init ([0] class TestLib.IFoo`1<class TestLib.IChild> bar)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  box        !!T
  IL_0007:  call       void TestLib.Ext::DoSomethingElse(class TestLib.IFoo`1<class  TestLib.IBase>)
  IL_000c:  nop
  IL_000d:  ret
} // end of method Ext::DoSomething

you would discover that this compiles. And what does reflector resolve this as in C#?

public static void DoSomething<T>(this T foo) where T: IFoo<IChild>
{
    foo.DoSomethingElse();
}
Ethan Cabiac
  • 4,943
  • 20
  • 36
1

Don't know why it doesn't compile, but is this an acceptable alternative?

public static void DoSomethingElse<T>(this IFoo<T> foo) where T : IBase
{
}
default.kramer
  • 5,943
  • 2
  • 32
  • 50
  • Thanks for the workaround, but similar to oleksii's suggestion I'd rather not add more type parameters (since I have some already, and the type then couldn't be inferred, which is very annoying since I'm working with long typenames). – Cameron May 17 '11 at 18:25
1

Your piece of code

public static void DoSomethingElse(this IFoo<IBase> foo)
{
}

makes DoSomethingElse available only on IFoo<IBase> instances, what foo obviously isn't, since it's a IFoo<IChild>. The fact that IChild derives from IBase doesn't make IFoo<IChild> derive from IFoo<IBase>. So foo unfortunately cannot be considered as a kind of IFoo<IBase>, and DoSomethingElse therefore can't be invoked on it.

But this problem can easily be avoided if you slightly change your extension method this way :

public static void DoSomethingElse<T>(this IFoo<T> foo) where T : IBase
{
}

Now it compiles and all works fine.

The most interesting part is that DoSomethingElse(foo); compiles when called with a static method syntax but not with an extension method syntax. Obviously with a regular static method style call, generic covariance works well : the argument foo is typed as a IFoo<IBase> but can be assigned with a IFoo<IChild>, then the call is okay. But as an extension method, due to the way it is declared DoSomethingElse is made only available on instances formally typed as IFoo<IBase> , even if it would be compliant with IFoo<IChild>, so this syntax doesn't work on IFoo<IChild> instances.

Ssithra
  • 710
  • 3
  • 8
  • Thanks for your answer. It's quite plausible, except that it doesn't explain why the extension method can be called on `bar` (which is still an `IFoo`). It seems the implicit conversion from `IFoo` to `IFoo` *does* work even for extension method syntax, but not when it's a generic constraint specifying the type. – Cameron May 26 '11 at 22:57
  • I missed this point. You are perfectly right. The difference in having **DoSomethingElse** available or not is not a matter of call style as I thought but of whether the instance is explicitely typed with a compliant type or with a generic type : the compiler is not smart enough to realize that the generic type is in fact a kind of `IFoo` on which **DoSomethingElse** can apply. – Ssithra May 27 '11 at 08:21
0

It does not compile for the reason that it is complaining about 'TFoo' does not contain a definition for 'DoSomethingElse'

Your DoSomething is not defined to TFoo but for IFoo<IBase> and thus also for IFoo<IChild>.

Here are few changes I did. Have a look at what variants compile.

public interface IBase { }
public interface IChild : IBase { }

public interface IFoo<out T> where T : IBase { }

public static class FooExt
{
    public static void DoSomething<TFoo>(this TFoo foo)     where TFoo : IFoo<IChild>
    {
        IFoo<IChild> bar = foo;
        //Added by Ashwani 
        ((IFoo<IChild>)foo).DoSomethingElse();//Will Complie
        foo.DoSomethingElseTotally(); //Will Complie

        //foo.DoSomethingElse();    // Doesn't compile -- why not?
        bar.DoSomethingElse();      // OK
        DoSomethingElse(foo);       // Also OK!

    }

    public static void DoSomethingElse(this IFoo<IBase> foo)
    {
    }

    //Another method with is actually defined for <T>
    public static void DoSomethingElseTotally<T>(this T foo) 
    { 
    }

So hopefully it makes a bit more sense what compiles and what not and it is not a compiler bug.

HTH

Ash
  • 2,531
  • 2
  • 29
  • 38
  • I don't expect `TFoo` to have a `DoSomethingElse()` method, since `IFoo` doesn't have that method. The full error message is: 'TFoo' does not contain a definition for 'DoSomethingElse' *and the best extension method overload has some invalid arguments*. I'm expecting the compiler to use that extension method -- the compiler can resolve the call just fine when the constraint (and `DoSomethingElse`'s parameter) is changed to a regular, non-covariant interface (e.g. `ICloneable`). – Cameron Jun 09 '11 at 14:37
  • Well as of now the complier does not do so. It can for non covariant interfaces as you said but I dont see a way for the compiler to do this in this case. – Ash Jun 09 '11 at 15:31
  • Right. Hence my question asking whether this is a bug or not :-) – Cameron Jun 09 '11 at 16:21
0

The problem is that variance only works on reference types or identity conversions, from the spec (section 13.1.3.2):

A type T<A1, …, An> is variance-convertible to a type T<B1, …, Bn> if T is either an interface or a delegate type declared with the variant type parameters T<X1, …, Xn>, and for each variant type parameter Xi one of the following holds:
•         Xi is covariant and an implicit reference or identity conversion exists from Ai to Bi
•         Xi is contravariant and an implicit reference or identity conversion exists from Bi to Ai
•         Xi is invariant and an identity conversion exists from Ai to Bi

The compiler can't verify that TFoo isn't a struct that implements IFoo<IChild>, so it doesn't find the desired extension method. Adding a class constraint to DoSomething doesn't fix the problem either, as value types still inherit from object, therefore satisfying the constraint. IFoo<IChild> bar = foo; and DoSomethingElse(foo); both work because each have an implicit cast from foo to IFoo<IChild>, which is a reference type.

I would ask the same question that Mike Strobel asked in the comments above: why not change your DoSomething signature from

public static void DoSomething<TFoo>(this TFoo foo)
        where TFoo : IFoo<IChild>

to

public static void DoSomething<TFoo>(this IFoo<IChild> foo)

You don't seem to gain anything by making the method generic.

A few of the posts I read on the topic:

Generic extension method : Type argument cannot be inferred from the usage

Eric Lippert - Constraints are not part of the signature

C# generics type constraint

Community
  • 1
  • 1
WarrenG
  • 3,084
  • 16
  • 10
  • Your answer rests on that excerpt of the spec, which, if you read a little more carefully, actually does *not* say anything about reference types. It talks about implicit reference *conversions* (see §6.1.6 of the spec, which `IChild` -> `IBase` satisfies). Also, the [`class` constraint](http://msdn.microsoft.com/en-us/library/d5x73970.aspx) *does* constrain to only reference types (not structs). Additionally, `IFoo` is not a reference type (it's an interface that a struct could implement). – Cameron Jun 09 '11 at 23:36
  • Also, thanks for pointing out Mike's comment; I misunderstood it the first time. FacticiusVir did ask the same question in the comments of the original post, though. I answered there that I *am* using the generic parameter, so that I can specify additional constraints on `TFoo` besides the `IFoo` one. – Cameron Jun 09 '11 at 23:38
  • I should have included a bit more of the spec in my post. Under section 4.2, a reference type is listed as a class type, an **interface type**, an array type, or a delegate type, so `IFoo` is in fact a reference type. In the `constraint` link you posted it says as much. – WarrenG Jun 10 '11 at 00:05
  • @Warren: Interesting. This deserves its own question -- an interface is a reference type, but structs (value-types) can implement interfaces. Does that mean structs exposed through their interface are reference types? In any case, this is all tangent to my original question -- it's nothing to do with reference/value types, since there is no compile error when `IFoo` does not take a type parameter (try using e.g. `where TFoo : ICloneable` instead -- it will compile fine). – Cameron Jun 10 '11 at 03:32
  • @Cameron: when you cast a struct to an interface type, the struct is boxed, so a copy is made and referred to by the interface type. If you change IFoo and remove the type parameter, then there is no variance, correct? Or did I misunderstand your comment? – WarrenG Jun 10 '11 at 05:16
  • @Cameron: After a bit more playing with this in a demo project I see that you're right about this not being about reference/value types. There seems to be something about the inferred type in `DoSomething` that doesn't want to play nicely with the specified type in `DoSomethingElse`. It's curious that if both extension methods are generic, even with DoSomethingElse constrained by the base interface like this `DoSomethingElse(this TFoo foo) where TFoo : IFoo`, the code compiles without problem. – WarrenG Jun 10 '11 at 06:43
  • @Warren: Right. This behaviour apparently only happens under *very* specific circumstances. – Cameron Jun 10 '11 at 12:53