.NET接口的二进制兼容性

4

假设我们在C#中定义了一个基本接口:

interface IBase
{
    int Prop1 { get; set }
    string Prop2 { get; set }
}

然后我们有以下派生接口:
interface ISub1: IBase
{
    int Prop3 { get; set }
}

这些接口在API程序集中定义,定制应用程序编译和运行时使用该程序集。(该程序集还包括未公开的类来实现这些接口和用于获取实例的公共工厂方法)。所有现有代码都使用ISub1,没有直接引用IBase的现有代码。这样做是为了预计我们可能最终想引入第二个派生接口ISub2,作为ISub1的同级接口,并且现在已经实现。不幸的是,我们发现ISub2不应该包含Prop2(只包含Prop1和一些其他独特的属性),因此我们希望将该属性“降级”到ISub1中,从而得到以下修订后的接口:
interface IBase
{
    int Prop1 { get; set }
}

interface ISub1: IBase
{
    string Prop2 { get; set }
    int Prop3 { get; set }
}

interface ISub2: IBase
{
    string Prop4 { get; set }
}

考虑到IBase没有消费者,我们应该可以毫无顾虑地这样做(我相信在Java中我们可以这样做),但是当我们试图这样做时,我们遇到了与旧接口定义编译的代码的二进制兼容性问题。具体来说:

ISub1 s1 = ... // get an instance
s1.Prop2 = "help";

当针对新接口定义运行此代码时,会出现以下异常:

System.MissingMethodException:找不到方法:'Void MyNamespace.IBase.set_Prop2(System.String)'。

请注意引用了`IBase`。我认为这是因为似乎对`ISub1.set_Prop2`的调用已经编译成与实际引入`Prop2`的地方即`IBase`进行了紧密绑定导致的。
有人能帮我摆脱这个困境吗?也就是说,是否有一种重新设计接口的方式,使得`ISub2`的定义“干净”(不包括多余的`Prop2`)?要求所有现有应用程序重新编译是不可能的。

你有些卡住了。 - SLaks
1
我使用C#编写面向对象程序已经超过8年了,我不明白你的问题。你应该检查接口是否被更改,并且你的构建版本具有相同的.Net/接口版本。而且,在C#/net中没有二进制vtable。 - Croll
如果IBase定义了Prop2,而ISub2实现了IBase,则ISub2也必须实现Prop2。这似乎是一个非常直接的问题。 - Brian Driscoll
你能总结一下之前和之后的结构吗?从你的描述中,Prop2是在哪里定义的不太清楚。 - antiduh
进行了一些编辑,以使目标更清晰(希望如此)。 - mrcs
5个回答

2

这种方法可能有些取巧,不确定是否有效,但或许值得一试。

interface IBase0
{
    int Prop1 { get; set; }
}
interface IBase : IBase0
{
    int Prop1 { get; set; }
    string Prop2 { get; set; }
}
interface ISub1: IBase
{
    int Prop3 { get; set; }
}
interface ISub2 : IBase0
{
    int Prop4 { get; set; }
}

是的,谢谢。我应该提到我尝试过那个方法,虽然它是一种可能的但不太优雅的解决方案。 - mrcs

1
通过在TryRoslyn中编写,很容易看出基于将属性放置在接口中的位置存在差异:
给定:
interface ISub1A: IBaseA
{
    int Prop3 { get; set; }
}

interface IBaseA
{
    int Prop1 { get; set; }
    string Prop2 { get; set; }
}

interface ISub1B: IBaseB
{
    int Prop3 { get; set; }
    string Prop2 { get; set; }
}

interface IBaseB
{
    int Prop1 { get; set; }
}

并且

ISub1A a = null;
a.Prop2 = "Hello";

ISub1B b = null;
b.Prop2 = "Hello";

请注意,在这两种情况下,我在C#代码中使用的是ISub1*接口。

生成的IL代码为:

IL_0001: ldstr "Hello"
IL_0006: callvirt instance void IBaseA::set_Prop2(string)
IL_000b: ldnull
IL_000c: ldstr "Hello"
IL_0011: callvirt instance void ISub1B::set_Prop2(string)

因此,IL代码“正确地”解析到实际定义属性的接口。

1
基本上,这个:
interface ISub1: IBase

只是说“任何实现ISub1的类也将保证实现IBase”。每个接口中定义的方法不会混合在一起,这也意味着“ISub1包含3个属性:Prop1 - Prop3”。所以这就是为什么它不起作用的原因。目前定义的ISub1需要恰好一个名为Prop3的属性。

0

0

虽然这个问题很旧了,但我想提到一个类似的问题,当我将接口拆分为基础类型和继承类型时遇到了问题。因为它是与相同主要发布版本的Nuget包的一部分,所以必须向下兼容以前的版本。我通过使用“new”关键字在原始接口中复制成员来解决它。

提取基础接口后出现MissingMethodException


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