I would like to detect breaking changes in .NET code (specifically C#) whenever TFS builds a solution. If there are any breaking changes (such as outlined in "A definite guide to API-breaking changes in .NET") between the code being checked in and the version in the most recent successful build, I would like to know about it. A breaking change needn't cause the build to fail. Short of writing an app that uses reflection to compare two versions of the same assembly, how can this be done?
-
Linked question: http://stackoverflow.com/questions/2377855/tool-for-backwards-compatibility-for-the-c-net-api – linuxbuild Aug 31 '11 at 07:46
4 Answers
To elaborate a bit on James and Adam answers, I'd like to provide details concerning detecting breaking changes with NDepend and its code query and rule capabilities. Disclaimer: I am one of the developers of the tool
NDepend has evolved and its query language as well. If you download NDepend trial and analysis the two versions of your code base where you'd like to search for breaking change, have a look in the default code rules group API Breaking Changes for the following CQLinq rules.
- API Breaking Changes: Types
- API Breaking Changes: Methods
- API Breaking Changes: Fields
- API Breaking Changes: Interfaces and Abstract Classes
- Broken serializable types
- API: New publicly visible types
- API: New publicly visible methods
- API: New publicly visible fields
Executing one of these code rule looks like for example (diff between NUnit v2.5.8 and v2.5.3):

- 13,237
- 6
- 61
- 92
Yes, I would (and do) use NDepend for this. I work on a product which provides an extendable API for developers. As such we need to make sure that between releases, we do not remove functionality that those developers may depend on. The flip-side, is that we need the flexibility to grow the product without massive constraints around reversioning.
Some things you will want to consider.
- Changing the version of a referenced DLL should be considered a breaking change.
- removing/changing members breaks backwards compatibility.
- adding members breaks forwards compatibility (some people just consider 'added members' as safe, but it does have a risk associated).
- Change file version with every build, you will need it at some point.
- Consider writing contracts that define your 'public API'. These will be the members that you need to support outside of the organisation. Think of them as interoperability boundaries. It then allows your implementation classes to have public members, which arent in the API (hence considered 'unsupported'), so you can change them without worrying about breaking the extensibility API. Extending the API consists of writing a new interface (with a version number in the interface name) which DOES NOT derive from the prior version of interface (derivation prevents you from fully deprecating members, and creates hell when it comes time to implement multiple interface versions in a single class.
- Dont forget about Attributes, changes to them may not break static compatiblity, but could affect the runtime.

- 4,159
- 4
- 32
- 53
-
Elaborate on #1? What if my Apple.dll depends on v1 of Bear.dll, and I upgrade to v1.2 of Bear.dll, and verify that Bear.dll doesn't expose any breaking changes as per the other #2-6? Users of Apple.dll should not experience any breakage as a result correct? So long as I ensure all referenced dll's also meet the same criteria all the way up the chain anytime I update a referenced DLL, there shouldn't be a problem correct? – AaronLS Apr 02 '14 at 22:16
-
1Hi AaronLS. This is a good question, and I should have originally elaborated on #1. Risk arises if public functionality of Apple.dll `returns types` that are declared within Bear.dll. If these assemblies are strongly typed, then a product depending on Apple will expect that function call to return (fully qualified type name) "Type, Bear, v1.1". If you have "hot fixed" (changed but not reversioned) your Apple.dll, it will now return "Type, Bear, v1.2" and the runtime will throw a class cast exception - because strongly-named types are qualified by all name-components, including version. – Adam Apr 06 '14 at 09:31
Unit tests. They provide a way to assert 'this is what client code expects'. You can have TFS run unit tests when you build.

- 1
- 1

- 28,047
- 29
- 99
- 127
-
6Two concerns here (and I am a huge proponent of unit testing!) The first is you'd have to have 100% coverage, enforced by convention. The second is that unless the tests were maintained outside of the solution, any breaking refactor would also change the unit tests, thus ignoring the problem. – jalbert Aug 30 '11 at 20:40
-
Unfortunately, unit tests only find *source breaking changes*, not *binary breaking changes* (such as adding an optional parameter to a method signature). – Heinzi Oct 12 '20 at 15:45
Patrick Smacchia of NDepend fame posted about this ~3.5 years ago.
http://codebetter.com/patricksmacchia/2008/01/20/avoid-api-breaking-changes/
He mentions LibCheck and (obviously) NDepend, and a comment mentions one more.
Since it's been 3.5+ years since, there may be better options available these days (LibCheck is well over 6 years old), but those should be a start.

- 13,429
- 2
- 40
- 64