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

发布于 2024-11-07 11:55:59 字数 1013 浏览 6 评论 0原文

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

假设我有这个接口结构:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

懵少女 2024-11-14 11:55:59

引用C#规范:

7.6.5.2 扩展方法调用

在方法调用(§7.5.5.1)中
其中一种形式

表达式。标识符 ( )

表达式。标识符(args)

表达式。标识符<类型参数 > ( )

表达式。标识符<类型参数 > (参数)

如果正常处理
调用发现不适用
方法,尝试处理
作为扩展方法的构造
调用。如果 expr 或任何参数
具有编译时类型动态
扩展方法将不适用。

目标是找到最好的
type-name C,这样对应的
静态方法调用可以采取
地点:

C .标识符(expr)

C .标识符(expr,args)

C .标识符<类型参数 > (expr)

C .标识符<类型参数 > (expr,args)

扩展方法Ci.Mj符合条件
如果:

· Ci 是一个非泛型,
非嵌套类

·Mj的名字是标识符

· Mj 可以访问并且
适用于当应用于
参数作为静态方法,如图所示
上面

· 隐含的身份,
存在引用或装箱转换
expr 到第一个的类型
Mj的参数。

由于 DoSomethingElse(foo) 可以编译,但 foo.DoSomethingElse() 不能编译,因此扩展方法的重载解析中似乎存在编译器错误:存在来自 <代码>foo到IFoo

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>.

逆光下的微笑 2024-11-14 11:55:59

你能在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>

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>
渔村楼浪 2024-11-14 11:55:59

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

尽管 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();
}

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();
}
情绪失控 2024-11-14 11:55:59

不知道为什么它不能编译,但这是一个可以接受的替代方案吗?

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

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
{
}
扮仙女 2024-11-14 11:55:59

您的代码片段

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

使 DoSomethingElse 仅在 IFoo 实例上可用,而 foo 显然不是,因为它是 IFoo< ;IChild>IChild 派生自 IBase 的事实并不意味着 IFoo 派生自 IFoo。因此,遗憾的是 foo 不能被视为一种 IFoo,因此不能在其上调用 DoSomethingElse

但是,如果您以这种方式稍微更改扩展方法,则可以轻松避免这个问题:

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

现在它可以编译并且一切正常。

最有趣的部分是 DoSomethingElse(foo); 在使用静态方法语法调用时进行编译,但使用扩展方法语法则不会进行编译。显然,对于常规静态方法样式调用,泛型协方差效果很好:参数 foo 的类型为 IFoo,但可以使用 IFoo< 进行赋值。 IChild>,那么调用就可以了。但作为扩展方法,由于其声明方式, DoSomethingElse 仅在正式类型为 IFoo 的实例上可用,即使它符合 < code>IFoo,因此此语法不适用于 IFoo 实例。

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.

微凉 2024-11-14 11:55:59

它无法编译,因为它抱怨'TFoo'不包含'DoSomethingElse'的定义

您的DoSomething未定义为TFoo,而是为IFoo 因此也适用于 IFoo

以下是我所做的一些更改。看看编译了哪些变体。

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

因此,希望它能更有意义地解释什么可以编译,什么不能编译,并且这不是编译器错误。

华泰

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

秋风の叶未落 2024-11-14 11:55:59

问题在于,方差仅适用于引用类型或身份转换,根据规范(第 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>,所以它找不到所需的扩展方法。向 DoSomething 添加 class 约束也不能解决问题,因为值类型仍然继承自 object,因此满足约束。 IFoo; bar = foo;DoSomethingElse(foo); 都可以工作,因为它们都有从 fooIFoo 的隐式转换,这是一个引用类型。

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

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

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

似乎没有通过使方法通用而获得任何东西。

我读到的一些关于该主题的帖子:

通用扩展方法:无法从用法推断类型参数

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

C# 泛型类型约束

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文