开发者

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 overl开发者_高级运维oad '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
{
}


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.


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


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜