Breaking Changes in C#
Due to the nature of the language, there are some API changes that almost inevitably lead to breaking changes in C#.
Change constructor parameters
While you can easily make another constructor, marking the old one(s) as obsolete, if you use an IOC that allows only a single public constructor, you’re forced to either
- remove the obsolete constructor or
- mark the obsolete constructor as
In either case, the user has a compile error.
There are several known issues with introducing new methods or changing existing methods on an existing interface. For many of these situations, there are relatively smooth upgrade paths.
I encountered a situation recently that I thought worth mentioning. I wanted to introduce a new overload on an existing type.
Suppose you have the following method:
bool TryGetValue<T>( out T value, TKey key = default(TKey), [CanBeNull] ILogger logger = null );
We would like to remove the
logger parameter. So we deprecate the method above and declare the new method.
bool TryGetValue<T>( out T value, TKey key = default(TKey) );
Now the compiler/ReSharper notifies you that there will be an ambiguity if a caller does not pass a
logger. How to resolve this? Well, we can just remove the default value for that parameter in the obsolete method.
bool TryGetValue<T>( out T value, TKey key = default(TKey), [CanBeNull] ILogger logger );
But now you’ve got another problem: The parameter
logger cannot come after the
key parameter because it doesn’t have a default value.
So, now you’d have to move the
logger parameter in front of the key parameter. This will cause a compile error in clients, which is what we were trying to avoid in the first place.
In this case, we have a couple of sub-optimal options.
- Multiple Releases
Use a different name for the new API (e.g.
TryGetValueExà la Windows) in the next major version, then switch the name back in the version after that and finally remove the obsolete member in yet another version.
- in version n,
TryGetValue(with logger) is obsolete and users are told to use
- in version n+1,
TryGetValueEx(no logger) is obsolete and users are told to use
- in version n+2, we finally remove
This is a lot of work and requires three upgrades to accomplish. You really need to stay on the ball in order to get this kind of change integrated and it takes a non-trivial amount of time and effort.
We generally don’t use this method, as our customers are developers and can deal with a compile error or two, especially when it’s noted in the release notes and the workaround is fairly obvious (e.g. the
loggerparameter is just no longer required).
- in version n,
- Remove instead of deprecating
- Accept that there will be a compile error and soften the landing as much as possible for customers by noting it in the release notes.