【问题标题】:Trying to understand method signature changes spanning assemblies试图了解跨程序集的方法签名更改
【发布时间】:2015-06-13 22:15:53
【问题描述】:

我们在促销时遇到了一个奇怪的问题,我希望我能用代码来解释它。我想了解它为什么会这样。

组装 1

public static class Foo
{
    public static string DoStuff()
    {
        // Do something

        return "some string";
    }
}

组装 2:

public class Bar
{
    public void SomeMethod()
    {
        // I realize the below is not what should be done for catching exceptions, as the exception is never thrown due to the return, and seems unnecessary either way... 
        // this is inherited code and has not been modified to correct.

        try
        {
            var someValue = Foo.DoStuff();
        }
        catch (Exception)
        {
            return;
            throw;
        }
    }
}

要求发生了变化,因此 DoStuff 需要接受一个参数,该参数的值会稍微改变行为。请注意,只有程序集 1.Foo 正在更改。

新符

public static class Foo
{
    public static string DoStuff(bool someBool = false)
    {
        // Do something

        return "some string";
    }
}

这重新编译得很好,Assembly 2 能够成功地使用更改后的方法签名而没有抱怨。我的签入已执行,并提升了有更改的项目 dll(请注意,这只是 Assembly 1 dll)。

升级后,我们发现 Assembly 2 在 Foo.DoStuff() 调用上失败 - 不幸的是,我无法提供异常,因为上面的代码正在吞噬它。

尽管在 Assembly 2 中没有实际代码更改,但它似乎确实对重新编译时的 dll 产生了影响,即使方法签名(至少在我看来)是相同的,因为为新的提供了默认值参数。

我使用 dotnet peek 来查看“旧 dll”和“新 dll”(即使没有对该程序集进行任何代码更改,并且确实注意到旧 DLL 中的两个 DLL 存在差异,默认没有提供 value 参数,但在重新编译时,提供了默认 value 参数。

我想我的问题可以归结为:

  1. 为什么程序集 2 上的已编译代码会根据(至少我认为)应该是透明的方法签名进行更改?
  2. 避免这种情况的方法是否只是“部署一切”?而不是尝试根据代码更改进行部署?请注意,未签入 DLL,因此程序集 2 没有实际的“代码更改”,尽管 DLL 根据对程序集 1 的更改而有所不同。

【问题讨论】:

  • 避免这种情况的另一种方法是保留无参数方法并让它使用默认值调用新方法,而不是在方法签名中指定默认值:public static string DoStuff() { return DoStuff(false); } .从该捕获中删除return 也将有所帮助(或至少首先记录异常)。
  • @RufusL 如果我能再次做到这一切,知道我现在所知道的,那肯定是我会采取的方法;)

标签: c# .net dll dependencies .net-assembly


【解决方案1】:

为什么程序集 2 上的编译代码会根据(至少我认为)应该是透明的方法签名进行更改?

不,不应该。当您没有指定与可选参数对应的参数时,默认值将在调用站点提供 - 即在您的情况下为程序集 2。这就是可选参数在 C# 中的工作方式。这是一种痛苦,但这就是生活。

避免这种情况的方法是“部署一切”吗?

是的。基本上,汇编 2 中源代码的含义已经改变,尽管代码本身没有改变 - 所以编译后的代码已经改变,需要重新部署。

在其他情况下,您也可以看到类似的细微破坏性变化 - 相同的旧“客户端”代码针对新的“接收”代码进行编译,但具有不同的含义。两个例子:

  • 假设您将方法签名从 Foo(long x) 更改为 Foo(int x),但在调用站点,您只将其称为 Foo(5)... 在两种情况下都可以编译,但代码不同。

  • 假设您已将方法签名从Foo(int x, int y) 更改为Foo(int y, int x),并且您再次调用Foo(x: 5, y: 2)...,代码的含义从@ 更改987654327@ 到 Foo(2, 5),如果你明白我的意思。针对新的“接收”代码重新编译未更改的源代码将改变行为。

  • 假设您在程序集 1 中有一个常量,例如public const string Foo = "Foo";。如果您将其更改为public const string Foo = "Bar",但不使用该常量重新编译程序集,则这些程序集仍将使用“Foo”值。 (这也适用于可选参数的默认值。)

【讨论】:

  • 更好的解决方案是保留DoStuff() 的无参数版本并让它使用默认值调用参数化重载吗?比如:public static string DoStuff() { return DoStuff(false); }?还是这有它自己的一系列问题?
  • @RufusL:这是一种更向后兼容的方式,是的 - 这真的取决于重新部署所有东西是否真的是一个问题。,
  • 谢谢,我之前没有想过你的“假设”场景,这很有帮助!
猜你喜欢
  • 2018-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-06
  • 2012-01-10
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多