This blog post will focus on source incompatibilities.
Part 1 was all about a simple, but hard to follow, rule: “don’t make changes to your library that alter its behavior”. That was about it, just don’t do it!
Part 2 and 3 are about additional types of backward compatibility that you may want to guarantee to your customers. You don’t have to, many products don’t, but you should decide in advance and set expectations accordingly.
This next type of incompatibility is the most straightforward: when your customers update your library, their projects don’t compile anymore.
This is obviously annoying as your customers now have to scramble to update their code. On the other hand, this is way better than a silent behavioral change in your library: source incompatibilities never go unnoticed! We have already discussed in the previous post how you can even use the Obsolete attribute to force a source incompatibility and protect your users from a behavioral change:
[Obsolete("This class is obsolete, use SeverelyBuggedClassV2 instead.", error: true)]
public class SeverelyBuggedClass
If your goal is to never break source compatibility, be advised that you won’t be able to completely guarantee that. At the very least, you don’t have a way to control which type names are already used in your customer project and you may end up with a conflict when adding new classes to your library.
This is not the end of the world as the resulting error is straightforward and easy to fix:
Error CS0104 'Foo' is an ambiguous reference between 'Namespace1.Foo' and 'Namespace2.Foo'
Addressing this error may be extremely tedious and time consuming though. At the very least, make sure not to use names conflicting widely used .NET types from Microsoft (e.g., don’t name your class
An interesting corner case is when a new method signature (name and parameter types) conflicts with an extension method defined by your customer. This is actually a behavioral incompatibility because it won’t result in a compilation error but will silently switch your customer code to use the new method instead of the extension method!
Common Source Incompatibilities
Most types of source incompatibilities are pretty evident:
- Renaming or removing a type, property or method
virtual from a method
final to a class
- Changing a method, property or field to be
static or non-
- Adding a constructor with parameters to a class without constructors
- Adding non-optional parameters to a method
- Changing method parameter types (unless a implicit conversion is available, e.g., changing
long is ok)
- Changing property, field and method return types (unless a implicit conversion is available, e.g.,
short is ok)
- Changing method parameters modifiers (in, out, ref or removing params)
- Renaming a method parameter (this breaks the usage of named arguments)
- Adding type constraints on generic types
Go back and read the list again, I am sure there are a couple of entries you have overlooked.
(I know I had to go back and add to the list multiple times while writing the post…)
Photo by Mollybob, used under Creative Commons license.
Making any of these changes is only an issue for
public members of
public types (or
protected members of
public non-final types): if your customer can’t use what you have changed, you won’t break them. There is an exception to this rule: reflection.
Reflection allows to access types and members that would normally not be visible. This violates all encapsulation rules and, unless you have instructed your customers to use reflection on your library, is a very bad practice.
You may want to be explicit in your documentation and state that all private portions of your library can be changed without notice and without any backward compatibility guarantee. Except for that, I think most customers who use reflection on someone else’s library know that their code may break.
If you use reflection within your library (there are indeed some reasonable use cases for it), make sure you have unit tests for that code because you may easily break your own library when making changes.
Interfaces and Abstract Classes
While most source incompatibilities come from the removal of features that your customer is using, the addition of constraints is also a problem.
The most common source of this issue is the addition of methods or properties on
public interfaces or the addition of
abstract members to classes. This can easily be overlooked as “adding functionalities” but, if a customer is implementing the interface (or extending the
abstract class), they will now have to change their code to implement the new members.
Implicit Type Conversions
Unfortunately, there are very few tools to work around introducing source incompatibilities. For the most part, it is just a matter of making a good design in the first place and being careful when making code changes later.
One area where we have some language support is changing input and output types for methods, properties and fields. We can define implicit conversion operators to keep the change source compatible.
For example, let’s say that we have this method:
public class Calendar
public DateTime FindNextAppointment(DateTime start);
and we don’t like how the
DateTime type in .NET can be
Local or even
Unspecified, making this method error-prone to use.
We can change its parameter and return type to a different one if we provide implicit conversions:
public class Calendar
public UtcDateTime FindNextAppointment(UtcDateTime start);
public struct UtcDateTime
private readonly DateTime Time;
public UtcDateTime(DateTime time)
Time = time;
Time = time.ToUniversalTime();
throw new NotSupportedException
("UtcDateTime cannot be initialized with an Unspecified DateTime.");
public static implicit operator UtcDateTime(DateTime t) => new UtcDateTime(t);
public static implicit operator DateTime(UtcDateTime t) => t.Time;
This change is source compatible (not behaviorally compatible though because we are now throwing a
The next blog post will cover the pros and cons of guaranteeing binary compatibility to your customers as well as how to actually maintain binary compatibility for your library.