为什么这个通用扩展方法不能编译?
代码有点奇怪,请耐心等待(请记住,这种情况确实出现在生产代码中)。
假设我有这个接口结构:
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作为静态方法应用于参数时可以访问和应用
·从expr到Mj的第一个参数的types存在一个隐含的标识,引用或装箱转换。
由于DoSomethingElse(foo)
编译,但foo.DoSomethingElse()
没有,它似乎是一个重载parsing的扩展方法的编译器错误:从foo
到IFoo<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>
。 IChild
从IBase
派生的事实不会使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仍然从object
inheritance,因此满足约束。 IFoo<IChild> bar = foo;
和DoSomethingElse(foo);
两者都有效,因为每个人都有一个从foo
到IFoo<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约束