Click here to Skip to main content
15,885,985 members
Articles / Game Development / Unity

Breaking Changes and Unity

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
2 Jun 2015CPOL7 min read 4.8K  
Breaking changes and Unity

In the post, Design and Implementation Mistakes - Mostly .NET, one of the most important points was the "program to interfaces, not to implementations", in the sense that we must not use something that's not documented simply because we saw the actual implementation or discovered it somehow (like the actual type returned when the signature shows an abstract type, if the result is singleton or not, etc). The reason for this is to avoid applications from breaking if, in the future, there's a change in the implementation. The theory is that the code must be modifiable as long as the signature is the same and nothing must be broken by this. If something breaks when the implementation is changed (considering the new implementation is not bugged), then people were counting on implementation details.

This is extremely important when we deal with third-party libraries. We don't want our applications to break because a library implementation is changed. One of the ways to guarantee that is to copy the specific version of the library locally (so you simply don't have to worry about changes to the library, be them implementation details, signature changes or anything). Yet, we can't do that for everything. In particular, libraries coming with Windows and .NET are always shared and, even if developers at Microsoft try to keep things as compatible as possible, there are always ways to count on the wrong implementation detail.

So, let's see what is usually forbidden when changing any public library (this applies to anyone building public libraries and is related only to public and protected members):

  • Changing type or member names
  • Adding, removing or changing the order of parameters in a method
  • Changing a method return type, field type, property type, event type or parameter type
  • Removing any type or member
  • Changing a field to a property or the opposite
  • Changing the base class of a type
  • Removing a public interface from a type
  • Adding new abstract members to a type

On the opposite side, these are the things that are considered safe to do:

  • Add, remove or change anything that's private or internal
  • Add public types
  • Add public or protected members
  • Add method or constructor overloads with a different number of parameters
  • Override methods that weren't previously overridden
  • Implement new public interfaces

In the past, at least for C#, changing method parameter names was safe because no-one would ever use a parameter name on the invocation. Since C# added support to put the parameter name on the invocation, changing a parameter name is also forbidden.

From all of the things that we can or cannot do, the most complicated one is overloading. Overloading methods or constructors with the same number of parameters can become a breaking change to the source code because of ambiguity.

In particular, if null is accepted, it will become ambiguous when there are two or more overloads that accept nullable values. If a more specific version of a method is made, code that in the past called the less specific version may start calling the more specific overload when recompiled (sometimes this is fine, but not always).

Also, there's a gotcha when reflection is involved. Methods that always had overloads are surely not referenced by reflection using only the name, as that would always fail. Yet, methods that didn't have overloads may be referenced by reflection only by name and adding the overload will make the call ambiguous. Yet, should we avoid creating method overloads only because people may be using reflection imprudently?

Constructor Overload

Constructors may be overloaded like methods and they can also have ambiguity problems. What's worse with constructors is that, differently from methods, we can't give them a different name.

When using reflection, we usually don't have problems because the GetConstructor() method requires the parameter types to be provided, avoiding the ambiguity that we may have when using the GetMethod(methodName). Yet, that's not a guarantee. Some developers love to be lazy and, if the constructor has too many parameters, they will find the constructor by doing:

C#
var constructor = type.GetConstructors()[0];

Or:

C#
var constructor = type.GetConstructors().Single();

In the first case, if a new overload is added, maybe that new overload will be used instead of the expected constructor. On the second case, adding a new constructor to the type will throw.

If your application crashes when you install a new version of .NET and you were doing any of those calls, it is easy to say that it was your fault, right? You may not like it, yet it is but people using the application can still blame Microsoft for this.

Unity Container

Actually what inspired me to write this post was bug found in Windows 10. Actually, it is the kind of situation where "it is no-one's fault". The only thing that happened was that a new constructor was added to a type that previously only had a default constructor.

Everybody doing a normal new call, invoking the default constructor, will have no issues. Anyone using the var constructor = type.GetConstructors()[0]; will also not have problems because the default constructor is the one at index zero. Someone using the GetConstructor() method giving Type.EmptyTypes (or new Type[0]) as parameter will not have problems.

The only people that will have problems are people doing something like:

C#
var constructor = type.GetConstructors().Single();

Or people that are instantiating that class using Unity and registering such class using RegisterType() without specifying any injectionMembers.

Unity's logic is to use the constructor with most parameters, not the one with less parameters. In this case, Unity finds the new constructor, yet as it is not configured to provide a values for the new constructor's parameters, it fails.

Is this Unity's fault? Well... maybe we can consider that searching for the constructor with most parameters was a design flaw. If the RegisterType method only registered the default constructor when no parameters are provided, that error wouldn't occur.

Yet, Unity was made like that to be able to provide the best dependencies for all the parameters and some people count on the fact that Unity will throw if a dependency is missing, even if there's another constructor that Unity would be able to call.

That is:

  • From a Unity perspective, it is right to look for the constructor with most parameters;
  • From a library development perspective, it is OK to add a constructor overload when we have different construction options that can't be set after the object is instantiated;
  • From an application perspective, maybe the right thing to do would be to instruct Unity to always use the default constructor when instantiating a third-party type.

Yet, if users install a new version of Windows and their applications crash, they will probably blame Microsoft for introducing a breaking change.

At this moment, I don't know what will happen. We shouldn't be blocked from adding constructor overloads but a design choice made in Unity and the most used RegisterType() overload don't work fine with the change. Maybe that new constructor will be removed if it is considered that too many people may be negatively affected by this Unity's behavior. In any case, if you use Unity to instantiate third-party types that have a default constructor, remember to explicitly ask for the default constructor, or else bad things may happen in the future.

Going Against the Purpose

It is possible to think that requesting to explicitly use the default constructor is going against the purpose. If that was desirable, that should be the default, right?

Well, the problem is that when dealing with our own code and with 3rd-party code the expectations are different. In my own code, I might be adding new parameters to the existing constructors, or adding a new constructor with more parameters, because I just created a new functionality (maybe I added logging support to my classes) and I want the container to see that. If it fails to find a configuration, I want to be warned that I forgot something.

That is, during development, it is great to receive an exception when we forget to configure something.

Yet, when we "close" our development cycle, we don't want changes to external libraries to break our application. If an existing class now has a constructor that expects an ILogger (for example) and a default constructor (which was the one always used in the past), what's better? To crash the application on the user's face because that ILogger can't be resolved or to continue to use the old constructor? Well, considering that Unity doesn't know which libraries are third party ones, and which ones are made by us, it becomes our responsibility to make it explicit the constructor to use.

As it usually happens, a feature to make things easier and reduce our work can bite us in the long run.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
-- There are no messages in this forum --