15049

Trying to understand method signature changes spanning assemblies

Question:

We ran into a strange problem with a promotion and I'm hoping I'll be able to explain it with code. I want to understand why it behaves in the manner it is.

Assembly 1

public static class Foo { public static string DoStuff() { // Do something return "some string"; } }

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

Requirements changed so that DoStuff would need to take in a parameter, the value of which would change the behavior slightly. Note that only assembly 1.Foo is changing.

New Foo

public static class Foo { public static string DoStuff(bool someBool = false) { // Do something return "some string"; } }

This recompiled fine, and Assembly 2 was able to successfully utilize the changed method signature without complaint. My check in was performed, and project dlls that had changes were promoted (note this was only Assembly 1 dll).

After promotion we found out that Assembly 2 was failing on the Foo.DoStuff() call - unfortunately I cannot provide an exception as the above code was swallowing it.

Even though no actual code changed in Assembly 2, it did seem to have an impact on the dll on recompile, even though the method signature - at least in my mind - is the same due to providing a default value for the new parameter.

I used dotnet peek in order to peek at the "old dll" and the "new dlls" (even though no code change to that assembly and did note a difference in the two DLLs in that in the old DLL, the default value parameter was not supplied, but in the recompiled, the default value parameter was supplied.

I guess my question(s) boil down to this:

<ol><li>Why does the compiled code on assembly 2 change based on a method signature that (at least I think) should be transparent?</li> <li>Would the method of avoiding a situation like this just be "deploy everything"? Rather than trying to deploy based on code changes? Note that DLLs are not checked in, so there was no actual "code change" to assembly 2, though the DLL was different based on the changes to Assembly 1.</li> </ol>

Answer1:

<blockquote>

Why does the compiled code on assembly 2 change based on a method signature that (at least I think) should be transparent?

</blockquote>

No, it shouldn't. When you don't specify an argument to correspond with an optional parameter, the default value is provided at the call site - i.e. assembly 2 in your case. That's how optional parameters work in C#. It's a pain, but that's life.

<blockquote>

Would the method of avoiding a situation like this just be "deploy everything"?

</blockquote>

Yes. Basically, the <em>meaning</em> of the source in assembly 2 has changed even though the code itself hasn't - so the compiled code has changed, and it needs to be redeployed.

You can see subtle breaking changes like this in other cases, too - where the same old "client" code compiles against new "receiving" code, but has a different meaning. Two examples:

<ul><li>

Suppose you change a method signature from Foo(long x) to Foo(int x) but at the call site, you only call it as Foo(5)... that compiles in both cases, but to different code.

</li> <li>

Suppose you have change a method signature from Foo(int x, int y) to Foo(int y, int x) and you have a call of Foo(x: 5, y: 2)... again, the <em>meaning</em> of the code changes from Foo(5, 2) to Foo(2, 5), if you see what I mean. Recompiling the unchanged source code against the new "receiving" code will change the behaviour.

</li> <li>

Suppose you have a constant in assembly 1, e.g. public const string Foo = "Foo";. If you change that to public const string Foo = "Bar", but don't recompile assemblies <em>using</em> the constant, those assemblies will still use a value of "Foo". (This goes for default values of optional parameters, too.)

</li> </ul>

Recommend

  • onreadystatechange is not called in firefox
  • Python function optional arguments - possible to add as condition?
  • How to accept hash parameters in routes
  • How to make an elasticsearch query that filters on the maximum value of a field?
  • Is there a way to call library thread-local init/cleanup on thread creation/destruction?
  • Classic ASP URL Rewriting
  • Why can't I use non-integral types with switch [duplicate]
  • Efficient User-Agent Regex to find Safari in Python
  • Primefaces lazy datascroller calling load twice
  • Translating C# to PowerShell in InterIMAP
  • Compare struct to a constant in C
  • How to access meteor package name inside package?
  • Wrong labels when plotting a time series pandas dataframe with matplotlib
  • Rest Services conventions
  • SonarQube: Cannot deactivate rule with missing quality profile
  • Android application: how to use the camera and grab the image bytes?
  • Unable to decode certificate at client new X509Certificate2()
  • why xml file does not aligned properly after append the string in beginning and end of the file usin
  • Needing to do .toArray() to get output of mongodb .find() on key name not value
  • htaccess add www if not subdomain, if subdomain remove www
  • x64 applications using gdi+: what are the consequences on performance?
  • Converting a WriteableBitmap image ToArray in UWP
  • JSON response opens as a file, but I can't access it with JavaScript
  • Reading JSON from a file using C++ REST SDK (Casablanca)
  • Why value captured by reference in lambda is broken? [duplicate]
  • QLineEdit password safety
  • Accessing IRQ description array within a module and displaying action names
  • Java applet as stand-alone Windows application?
  • javascript inside java/jsp code
  • Validaiting emails with Net.Mail MailAddress
  • Which linear programming package should I use for high numbers of constraints and “warm starts” [clo
  • Javascript + PHP Encryption with pidCrypt
  • Symfony2: How to get request parameter
  • ORA-29908: missing primary invocation for ancillary operator
  • Calling of Constructors in a Java
  • Traverse Array and Display in markup
  • Transpose CSV data with awk (pivot transformation)
  • Why can't I rebase on to an ancestor of source changesets if on a different branch?
  • C# - Getting references of reference
  • How to CLICK on IE download dialog box i.e.(Open, Save, Save As…)