为什么这个通用扩展方法不能编译?
该代码有点奇怪,所以请耐心等待(请记住,这种情况确实出现在生产代码中)。
假设我有这个接口结构:
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)”具有一些无效参数
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
引用C#规范:
由于
DoSomethingElse(foo)
可以编译,但foo.DoSomethingElse()
不能编译,因此扩展方法的重载解析中似乎存在编译器错误:存在来自 <代码>foo到IFoo
。Quoting the C# specification:
Since
DoSomethingElse(foo)
compiles butfoo.DoSomethingElse()
doesn't, it seems like a compiler bug in overload resolution for extension methods: an implicit reference conversion exists fromfoo
toIFoo<IBase>
.你能在IFoo中定义DoSomethingElse吗?
更新
也许你可以更改签名
Can you define DoSomethingElse in the IFoo?
UPDATE
Maybe you can then change the signature
我发现证据表明这是一个“错误”。
尽管 CLR 语言不必支持 MSIL 中的所有可用功能,但事实是您尝试做的事情在 MSIL 中是有效的。
如果您打算将代码转储到 IL 中并使 DoSomething 方法如下所示:
您会发现它可以编译。在 C# 中,反射器如何解决这个问题?
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:
you would discover that this compiles. And what does reflector resolve this as in C#?
不知道为什么它不能编译,但这是一个可以接受的替代方案吗?
Don't know why it doesn't compile, but is this an acceptable alternative?
您的代码片段
使
DoSomethingElse
仅在IFoo
实例上可用,而 foo 显然不是,因为它是IFoo< ;IChild>
。IChild
派生自IBase
的事实并不意味着IFoo
派生自IFoo
。因此,遗憾的是 foo 不能被视为一种IFoo
,因此不能在其上调用DoSomethingElse
。但是,如果您以这种方式稍微更改扩展方法,则可以轻松避免这个问题:
现在它可以编译并且一切正常。
最有趣的部分是,因此此语法不适用于
DoSomethingElse(foo);
在使用静态方法语法调用时进行编译,但使用扩展方法语法则不会进行编译。显然,对于常规静态方法样式调用,泛型协方差效果很好:参数 foo 的类型为IFoo
,但可以使用IFoo< 进行赋值。 IChild>
,那么调用就可以了。但作为扩展方法,由于其声明方式,DoSomethingElse
仅在正式类型为IFoo
的实例上可用,即使它符合 < code>IFooIFoo
实例。Your piece of code
makes
DoSomethingElse
available only onIFoo<IBase>
instances, what foo obviously isn't, since it's aIFoo<IChild>
. The fact thatIChild
derives fromIBase
doesn't makeIFoo<IChild>
derive fromIFoo<IBase>
. So foo unfortunately cannot be considered as a kind ofIFoo<IBase>
, andDoSomethingElse
therefore can't be invoked on it.But this problem can easily be avoided if you slightly change your extension method this way :
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 aIFoo<IBase>
but can be assigned with aIFoo<IChild>
, then the call is okay. But as an extension method, due to the way it is declaredDoSomethingElse
is made only available on instances formally typed asIFoo<IBase>
, even if it would be compliant withIFoo<IChild>
, so this syntax doesn't work onIFoo<IChild>
instances.它无法编译,因为它抱怨'TFoo'不包含'DoSomethingElse'的定义
您的DoSomething未定义为TFoo,而是为
IFoo
因此也适用于IFoo
。以下是我所做的一些更改。看看编译了哪些变体。
因此,希望它能更有意义地解释什么可以编译,什么不能编译,并且这不是编译器错误。
华泰
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 forIFoo<IChild>
.Here are few changes I did. Have a look at what variants compile.
So hopefully it makes a bit more sense what compiles and what not and it is not a compiler bug.
HTH
问题在于,方差仅适用于引用类型或身份转换,根据规范(第 13.1.3.2 节):
编译器无法验证
TFoo
不是实现IFoo< 的结构;IChild>
,所以它找不到所需的扩展方法。向DoSomething
添加class
约束也不能解决问题,因为值类型仍然继承自object
,因此满足约束。IFoo; bar = foo;
和DoSomethingElse(foo);
都可以工作,因为它们都有从foo
到IFoo
的隐式转换,这是一个引用类型。我会问 Mike Strobel 在上面的评论中提出的同样的问题:为什么不将你的 DoSomething 签名从 更改为
你
似乎没有通过使方法通用而获得任何东西。
我读到的一些关于该主题的帖子:
通用扩展方法:无法从用法推断类型参数
Eric Lippert - 约束不是签名的一部分
C# 泛型类型约束
The problem is that variance only works on reference types or identity conversions, from the spec (section 13.1.3.2):
The compiler can't verify that
TFoo
isn't a struct that implementsIFoo<IChild>
, so it doesn't find the desired extension method. Adding aclass
constraint toDoSomething
doesn't fix the problem either, as value types still inherit fromobject
, therefore satisfying the constraint.IFoo<IChild> bar = foo;
andDoSomethingElse(foo);
both work because each have an implicit cast fromfoo
toIFoo<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
to
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