代码有点奇怪,所以请耐心等待(请记住,这个场景确实出现在生产代码中)。
说我有这个界面结构:
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;
//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
,它与泛型约束的类型相同,并在其上调用扩展方法。没有扩展方法语法调用扩展方法也没问题。
任何人都可以确认这是一个错误还是预期的行为?
谢谢!
仅供参考,这是编译错误(为清晰易读而删节的类型):
'TFoo'不包含'DoSomethingElse'的定义,最好的扩展方法重载'DoSomethingElse(IFoo)'有一些无效的参数
答案 0 :(得分:9)
引用C#规范:
7.6.5.2扩展方法调用
在方法调用(第7.5.5.1节)中 其中一种形式
expr。 identifier()
expr。标识符(args)
expr。标识符&lt; typeargs&gt; ()
expr。标识符&lt; typeargs&gt; (args)
如果正常处理了 调用找不到适用的 方法,尝试处理 构造作为扩展方法 调用。如果 expr 或任何args 有编译时类型动态, 扩展方法不适用。
目标是找到最好的 type-name C ,这样就相应了 静态方法调用可以采取 位:
C. identifier(expr)
C. identifier(expr,args)
C.标识符&lt; typeargs&gt; (expr)
C.标识符&lt; typeargs&gt; (expr,args)
扩展方法 Ci.Mj 符合条件 如果:
· Ci 是非通用的, 非嵌套类
· Mj 的名称是标识符
· Mj 是可访问的 适用于 参数作为静态方法如图所示 上述
·隐含身份, 存在引用或装箱转换 从 expr 到第一个的类型 参数 Mj 。
由于DoSomethingElse(foo)
编译但foo.DoSomethingElse()
没有编译,因此在扩展方法的重载解析中似乎是编译器错误:从foo
到IFoo<IBase>
存在隐式引用转换
答案 1 :(得分:5)
你能在 IFoo 中定义 DoSomethingElse 吗?
public interface IFoo<out T> where T : IBase
{
void DoSomethingElse();
}
<强>更新强>
也许你可以改变签名
public static void DoSomethingElse(this IFoo<IBase> foo)
=>
public static void DoSomethingElse<TFoo>(this TFoo foo)
where TFoo : IFoo<IChild>
答案 2 :(得分:3)
我发现这是一个“错误”的证据。
虽然CLR语言不一定支持MSIL中可用的所有功能,但事实是您尝试做的事情在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
你会发现这个编译。反射器如何在C#中解决这个问题?
public static void DoSomething<T>(this T foo) where T: IFoo<IChild>
{
foo.DoSomethingElse();
}
答案 3 :(得分:1)
不知道为什么不编译,但这是否可以接受?
public static void DoSomethingElse<T>(this IFoo<T> foo) where T : IBase
{
}
答案 4 :(得分:1)
你的代码
public static void DoSomethingElse(this IFoo<IBase> foo)
{
}
仅DoSomethingElse
个IFoo<IBase>
个实例可用,{strong> 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);
在使用静态方法语法调用时编译,但不使用扩展方法语法编译。显然,使用常规静态方法样式调用,泛型协方差效果很好:参数 foo 被输入为IFoo<IBase>
,但可以分配IFoo<IChild>
,然后调用就可以了。但作为一种扩展方法,由于它的声明方式DoSomethingElse
仅在正式输入为IFoo<IBase>
的实例上可用,即使它符合IFoo<IChild>
,所以这种语法不会不适用于IFoo<IChild>
个实例。
答案 5 :(得分:0)
它没有编译,因为它抱怨'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
答案 6 :(得分:0)
问题是方差仅适用于规范(第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
编译器无法验证TFoo
是否是实现IFoo<IChild>
的结构,因此它无法找到所需的扩展方法。向class
添加DoSomething
约束也不能解决问题,因为值类型仍然从object
继承,因此满足约束。 IFoo<IChild> bar = foo;
和DoSomethingElse(foo);
都有效,因为每个都有foo
到IFoo<IChild>
的隐式广告,这是一种参考类型。
我会问上一条评论中Mike Strobel提出的同样问题:为什么不改变你的DoSomething签名
public static void DoSomething<TFoo>(this TFoo foo)
where TFoo : IFoo<IChild>
到
public static void DoSomething<TFoo>(this IFoo<IChild> foo)
通过使该方法具有通用性,您似乎无法获得任何收益。
我在这个主题上读到的一些帖子:
Generic extension method : Type argument cannot be inferred from the usage