Due to the nature of the language, there are some API changes that almost inevitably lead to breaking changes in C#.
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
protected
.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.
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.
That is,
TryGetValue
(with logger) is obsolete and users are told to use TryGetValueEx
(no logger)TryGetValueEx
(no logger) is obsolete and users are told to use TryGetValue
(no logger)TryGetValueEx
.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 logger
parameter is just no longer required).