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
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>
.
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>
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();
}
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
{
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With