Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't this generic extension method compile?

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

like image 583
Cameron Avatar asked May 17 '11 17:05

Cameron


4 Answers

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>.

like image 52
Julien Lebosquain Avatar answered Sep 25 '22 01:09

Julien Lebosquain


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>
like image 23
oleksii Avatar answered Sep 24 '22 01:09

oleksii


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();
}
like image 44
Ethan Cabiac Avatar answered Sep 22 '22 01:09

Ethan Cabiac


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
{
}
like image 37
default.kramer Avatar answered Sep 26 '22 01:09

default.kramer