【问题标题】:Binary compatibility of .Net interfaces.Net 接口的二进制兼容性
【发布时间】:2015-06-23 14:12:43
【问题描述】:

假设我们在 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:找不到方法:'无效 MyNamespace.IBase.set_Prop2(System.String)'。

注意对IBase 的引用。我认为这是因为对ISub1.set_Prop2 的看似调用已编译为与Prop2 实际引入的位置紧密绑定,在IBase 中。

谁能帮我解决这个难题? IE。有没有办法重新考虑接口,以便 ISub2 的定义是“干净的”(不包括无关的 Prop2)?要求所有现有应用程序重新编译是不可能的。

【问题讨论】:

  • 你被卡住了。
  • 我用 C# 编写 OOP 代码超过 8 年,我不明白你的问题。您应该只检查接口是否未更改,并且您的构建具有相同的 .Net/interface 版本。而且.. C#/net 中没有二进制 vtable
  • 如果IBase定义了Prop2,而ISub2实现了IBase,那么ISub2也必须实现Prop2。这似乎是一个非常简单的问题。
  • 能否总结一下前后的结构?从您的文章中似乎有点不清楚 Prop2 是在哪里定义和定义的。
  • 进行了一些编辑以使其(希望)更清楚目标是什么。

标签: c# .net interface backwards-compatibility missingmethodexception


【解决方案1】:

有点老套,不确定它是否会起作用,但也许值得一试

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; }
}

【讨论】:

  • 好的,谢谢。我应该提到我尝试过,这是一个可能的但不优雅的解决方案。
【解决方案2】:

通过将其写入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 代码“正确”解析为真正定义属性的接口。

【讨论】:

    【解决方案3】:

    基本上是这样的:

    interface ISub1: IBase
    

    只是说“任何实现ISub1 的类都将承诺也实现IBase”。每个接口中定义的方法没有混合,这也意味着“ISub1 包含 3 个属性,Prop1 - Prop3”。

    所以这就是它不起作用的原因。 ISub1 当前被定义为只需要一个名为 Prop3 的属性。

    【讨论】:

      【解决方案4】:

      首先,您应该用implementing it explicitly 隐藏ISub2.Prop2。然后,根据ISub2 不应包含Prop2 的原因,您应该使用ObsoleteAttribute 属性弃用该实现,或者从两个访问器中抛出InvalidOperationException

      【讨论】:

        【解决方案5】:

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

        MissingMethodException after extracting base interface

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-03-04
          • 2015-10-11
          • 2011-08-03
          • 2011-08-09
          • 2020-05-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多