为什么VB.Net在接口上找不到扩展方法?

5

我有一个C#库,其中有一个扩展方法,类似于:

public interface ISomething { ... }
public class SomethingA : ISomething { ... }
public class SomethingB : ISomething { ... }

public static class SomethingExtensions 
{
    public static int ExtensionMethod(this ISomething input, string extra) 
    {
    }
}

如果从C#调用该扩展,它能正常工作;但如果从外部的VB.Net应用程序中调用,就会出现问题:

Dim something = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

这段代码可以编译通过,但在运行时会抛出异常:

类型 'SomethingB' 上的公共成员 'ExtensionMethod' 未找到。

如果将VB.Net代码更改为显式地将类型指定为接口,则可以正常工作:

Dim something as ISomething = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

为什么扩展方法可以在接口上工作,但不能在实现它的类上工作?如果我使用子类会有相同的问题吗?VB.Net对扩展方法的实现是否不完整?
在C#库中是否有任何操作可以使VB.Net在没有显式接口的情况下工作?
4个回答

5
当选项Infer关闭时,此代码...
Dim something = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

...与...相同。

Dim something As Object = Me.SomethingManager.GetSomething(key)
Dim result As Object = something.ExtensionMethod("extra")

由于somethingObject类型,因此它无法找到扩展方法,因为该方法未定义在Object类型上。

如果您设置Option Infer On,则会得到与C#的var关键字相同的结果。类型将自动推断。请注意,这也可能破坏现有代码,但是可以针对特定文件启用,例如Option Strict

最佳实践是将Option StrictOption Infer都设置为On。


我不认为“Option Infer”被设置,也没有默认设置为“On” - 我需要测试一下。 - Keith
啊,是的,如果您不指定一个推断选项,Visual Studio 默认为打开,但命令行编译器默认为关闭。Option Infer On 一贯比 Option Strict 更少地引起转换痛苦来解决这个问题。 - Keith
1
@Keith:从长远来看,那种转换的痛苦可能是一件好事。我真的鼓励你尽可能地使用Option Strict。 - Jon Skeet
@JonSkeet:我完全同意,但是对于后期修复来说它是一种痛苦的选择。Option Strict On绝对是最佳实践。而对于 VB.Net 的开发人员,Option Infer On能够在不需要更改大量代码的情况下解决问题。 - Keith

1

如果它抛出异常而不是给出编译时错误,那就说明你关闭了Option Strict...我不知道在这种情况下扩展方法会发生什么,因为它们通常在编译时解析,但是在关闭Option Strict时,你会遇到后期绑定的问题。

我建议你打开Option Strict,一切都会好起来...

(你还需要导入命名空间,如Richard的答案所述,但我假设你已经这样做了。如果你在打开Option Strict后忘记这样做,你将看到一个编译时错误。)


很不幸,我没有那个选项 - 他们已经有很多使用Option Strict Off的VB.Net代码。如果他们现在打开它,会出现大量编译错误。这是否意味着扩展方法不能与VB.Net中的任何后期绑定变量一起使用? - Keith
是的,打开 Option Strict On 只会在没有 as ISomething 时抛出编译时异常 - 他们想要的是后期绑定和扩展方法。他们希望 VB 中的 Dim 行为类似于 C# 中的 var。我想这是不可能的。 - Keith
2
@Keith 你可以在每个单独的代码文件上设置 Option Strict On。只需在文件顶部添加一行 Option Strict On,在任何其他内容之前。你不必为整个项目打开它。在遗留代码中这样做可能会非常令人生畏。 - Steven Doggart

1
感谢Jon指引我正确的方向,但这里有足够的内容需要一个完整的答案。
扩展方法是编译器的技巧,所以(在C#中):
var something = this.SomethingManager.GetSomething(key);
var result = something.ExtensionMethod("extra");

在编译时转换为以下内容:

ISomething something = this.SomethingManager.GetSomething(key);
int result = SomethingExtensions.ExtensionMethod(something, "extra");

作为类的方法出现的静态扩展方法只是编译器的巧妙处理。

问题在于 VB 中的 Dim 与 C# 中的 var 不同。由于 VB 的后期绑定,它更接近于 dynamic

因此,在 VB 中使用原始示例:

Dim something = Me.SomethingManager.GetSomething(key)
Dim result = something.ExtensionMethod("extra")

与C#不同,VB在运行时才确定something的类型,而使扩展方法起作用的编译器智能则不会发生。如果显式声明了类型,则可以解析扩展方法,但如果是后期绑定,则扩展方法永远无法工作。

如果他们使用Option Strict On(就像Jon建议的那样),那么他们被迫始终声明类型,早期绑定发生,并且编译器的智能使扩展方法起作用。这是最佳实践,但由于他们一直没有这样做,所以对他们来说这将是一个痛苦的变化。

这个故事的寓意:如果你希望任何来自VB领域的人接近你的API,请不要使用扩展方法 :-S


2
你可以通过设置“Option Infer On”来获得与C#相同的结果。请参阅我的回答。 - Meta-Knight

0

你导入了包含定义该命名空间的类的命名空间吗?

例如,如果没有 using System.Linq 命名空间,就看不到任何 LINQ to Objects 扩展方法。

Imports System.Linq

正如我在问题中所解释的,如果接口是明确声明的,则该方法有效。错误是运行时错误,而不是编译时错误。 - Keith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接