为什么这个通用扩展方法不能编译?

代码有点奇怪,请耐心等待(请记住,这种情况确实出现在生产代码中)。

假设我有这个接口结构:

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

使用这个扩展方法类来构build接口:

 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) { } } 

为什么DoSomething的注释行没有编译? 编译器非常高兴让我将foo赋值给bar ,它与通用约束的types相同,并调用扩展方法。 不使用扩展方法语法来调用扩展方法也没有问题。

任何人都可以确认,如果这是一个错误或预期的行为?

谢谢!

仅供参考,以下是编译错误(为便于阅读,删减了types):

“TFoo”不包含“DoSomethingElse”的定义,而最佳扩展方法重载“DoSomethingElse(IFoo)”有一些无效的参数

引用C#规范:

7.6.5.2扩展方法调用

在其中一个表单的方法调用(第7.5.5.1节)中

expr。 标识符()

expr。 标识符(args)

expr。 标识符<typeargs>()

expr。 标识符<typeargs>(参数)

如果调用的正常处理找不到可用的方法,则尝试将构造处理为扩展方法调用。 如果expr或任何参数都有编译时dynamictypes,则扩展方法将不适用。

目标是find最好的types名称C ,以便相应的静态方法调用可以发生:

C 。 标识符(expr)

C 。 标识符(expr,args)

C 。 标识符<typeargs>(expr)

C 。 标识符<typeargs>(expr,args)

在下列情况下,扩展方法Ci.Mj是符合条件的:

Ci是一个非generics的非嵌套类

· Mj的名称是标识符

·如上所示,将Mj作为静态方法应用于参数时可以访问和应用

·从exprMj的第一个参数的types存在一个隐含的标识,引用或装箱转换。

由于DoSomethingElse(foo)编译,但foo.DoSomethingElse()没有,它似乎是一个重载parsing的扩展方法的编译器错误:从fooIFoo<IBase>的隐式引用转换。

你可以在IFoo中定义DoSomethingElse吗?

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

UPDATE

也许你可以改变签名

 public static void DoSomethingElse(this IFoo<IBase> foo) => public static void DoSomethingElse<TFoo>(this TFoo foo) where TFoo : IFoo<IChild> 

我发现这是一个“错误”的证据。

尽pipeCLR语言不需要支持MSIL中提供的所有function,但实际上您正在尝试的是在MSIL中有效。

如果您想将代码转储到IL中并使DoSomething方法如下所示:

 .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 

你会发现这个编译。 那么reflection器如何在C#中parsing这个?

 public static void DoSomething<T>(this T foo) where T: IFoo<IChild> { foo.DoSomethingElse(); } 

不知道为什么不编译,但这是一个可以接受的select吗?

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

你的一段代码

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

使得DoSomethingElse仅在IFoo<IBase>实例上可用, foo显然不是,因为它是IFoo<IChild>IChildIBase派生的事实不会使IFoo<IChild>IFoo<IBase>派生。 所以foo不幸被认为是一种IFoo<IBase> ,因此DoSomethingElse不能被调用。

但是,如果稍微改变扩展方法,这个问题很容易避免:

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

现在编译,一切正常。

最有趣的部分是DoSomethingElse(foo); 使用静态方法语法调用时进行编译,但不使用扩展方法语法进行编译。 显然,对于常规的静态方法风格的调用,generics协方差效果很好: foo的参数types为IFoo<IBase>但可以赋予IFoo<IChild> ,那么调用就可以了。 但作为一种扩展方法,由于声明的方式使得DoSomethingElse仅在IFoo<IBase>types的实例上可用,即使它符合IFoo<IChild> ,所以该语法在IFoo<IChild>实例。

它不会因为抱怨“TFoo”不包含“DoSomethingElse”的定义而编译,

您的DoSomething没有被定义为TFoo,而是IFoo<IBase>以及IFoo<IChild>

这是我做的一些改变。 看看哪些变体编译。

 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) { } 

所以希望它能更有意义地编译什么,什么不是,它不是一个编译器错误。

HTH

问题是方差仅适用于参考types或标识转换,从规范(第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 

编译器不能validationTFoo不是实现IFoo<IChild> ,所以它找不到所需的扩展方法。 向DoSomething添加class约束并不能解决问题,因为值types仍然从objectinheritance,因此满足约束。 IFoo<IChild> bar = foo;DoSomethingElse(foo); 两者都有效,因为每个人都有一个从fooIFoo<IChild>的隐式IFoo<IChild> ,这是一个引用types。

我会问在上面的评论中,Mike Strobel提出的同样的问题:为什么不改变你的DoSomething签名

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

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

通过使这个方法变得通用,你似乎没有获得任何东西。

我在这个主题上阅读的一些post:

generics扩展方法:types参数不能从用法中推断出来

Eric Lippert – 约束不是签名的一部分

C#genericstypes约束