I am seeking detailed information about how API versioning operates within .NET/CLR, particularly regarding how certain modifications to APIs can lead to impacts on client applications. To begin with, let’s clarify some key definitions:
API Modifications: Any alteration to the publicly accessible definition of a type, which encompasses changes in public members. This includes renaming types or members, altering the base type of a type, adjusting the interfaces that a type implements, adding or removing members (including overloads), and modifying visibility or default parameter values, among others. It’s important to note that private member changes and internal code modifications that do not affect public aspects are not considered here.
Binary Break: Refers to an API modification that may prevent client assemblies compiled with an earlier API version from successfully loading the newer version. For example, this can happen if the method signature changes, even if the method can still be called in the same manner.
Source Break: This type of API change affects existing code, causing it to fail compilation against the old API version, but previously compiled assemblies will continue functioning. An instance of this would be introducing a new overload that renders previous method calls ambiguous.
Quiet Semantics Change: Such changes lead to existing code that still compiles without errors or warnings, yet the behavior subtly shifts, often by calling a different method than expected. For example, when a class implements a new interface that causes a different overload to be utilized during method resolution.
The main objective is to compile a list of both breaking and quiet semantics changes and to clearly explain their impacts, specifically identifying which programming languages are affected. Some modifications universally disrupt all languages, while others might only influence specific language constructs, particularly those relating to method overloading or implicit type conversions. Given the absence of a one-size-fits-all classification, exploring this on a language-by-language basis is necessary. Languages of particular interest are the ones bundled with .NET—C#, VB, and F#—as well as others like IronPython, IronRuby, Delphi Prism, etc. The most intricate cases, such as method overloading interactions, optional parameters, and type inference, will also be examined.
Examples to Explore:
Introducing Additional Method Overloads
Type: Source Break
Languages impacted: C#, VB, F#
API Pre-Alteration:
public class Sample
{
public void Execute(IEnumerable collection);
}
API Post-Alteration:
public class Sample
{
public void Execute(IEnumerable collection);
public void Execute(ICloneable object);
}
Client code that is functional before the change and broken afterward:
new Sample().Execute(new int[0]);
Adding New Implicit Conversion Operator Overloads
Type: Source Break
Languages impacted: C#, VB
Languages unaffected: F#
API Pre-Alteration:
public class Sample
{
public static implicit operator int();
}
API Post-Alteration:
public class Sample
{
public static implicit operator int();
public static implicit operator float();
}
Client code that works before the change but fails after:
void Execute(int value);
void Execute(float value);
Execute(new Sample());
Note: F# isn’t affected here due to its lack of support for operator overloading at the language level. Both explicit and implicit overloads must be called directly using their method names.
Adding New Instance Methods
Type: Quiet Semantics Change
Languages impacted: C#, VB
Languages unaffected: F#
API Pre-Alteration:
public class Sample
{
}
API Post-Alteration:
public class Sample
{
public void Execute();
}
Client code that experiences a quiet change in semantics:
public static class SampleExtensions
{
public static void Execute(this Sample sample);
}
new Sample().Execute();
Note: F# is not impacted since it does not support extension methods at the language level, requiring these to be called as static methods.