We like extension methods. When named accordingly they can both make the caller code clearer, and isolate static methods from classes on which they operate.
But when using extension methods, breaking change can happen, and this risk is very concrete, it actually just happened to us.
Since 2012, NDepend.API proposes a generic Append() extension:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace NDepend.Helpers { ///<summary> ///Provides a set of extension methods dedicated to enumerables. ///</summary> public static class ExtensionMethodsEnumerable { ///<summary> ///Append <paramref name="element"/> at the end of <paramref name="seq"/>. ///</summary> ///<typeparam name="T">The element type.</typeparam> ///<param name="seq">A sequence of elements.</param> ///<param name="element">The element to append.</param> ///<remarks>This extension method has a <i>constant</i> time complexity.</remarks> public static IEnumerable<T> Append<T>(this IEnumerable<T> seq, T element) { ... |
Two default rules use this extension method: Avoid namespaces dependency cycles and Avoid types initialization cycles
Last month, on Oct 17th 2017, Microsoft released .NET Framework v4.7.1 that implements .NET Standard 2.0. Around 200 .NET Standard 2.0 were missing in .NET Framewok v4.6.1, and one of those missing API is:
1 |
System.Linq.Enumerable.Append<TSource>(this System.Collections.Generic.IEnumerable<TSource>, TSource) |
Within NDepend, rules, quality gates, trend metrics … basically everything, is a C# LINQ query stored as textual and compiled and executed on-the-fly. Since the compilation environment uses both namespaces NDepend.Helpers and System.Linq, when running NDepend on top of the .NET Framework v4.7.1, both Append() extension methods are visible. As a consequence, for each query calling the Append() method, the compiler fails with:
1 2 3 4 |
error CS0121: The call is ambiguous between the following methods or properties: 'NDepend.Helpers.ExtensionMethodsEnumerable.Append<NDepend.CodeModel.IType>(System.Collections.Generic.IEnumerable<NDepend.CodeModel.IType>, NDepend.CodeModel.IType)' and 'System.Linq.Enumerable.Append<NDepend.CodeModel.IType>(System.Collections.Generic.IEnumerable<NDepend.CodeModel.IType>, NDepend.CodeModel.IType)' |
Hopefully a user notified us with this problem that we didn’t catch yet and we just released NDepend v2017.3.2 that fixes this problem Only one clean fix is possible to make it compatible with all .NET Framework versions: refactor all calls to the Append() extension method, into a classic static method invocation, with an explanatory comment:
1 2 3 |
// v2017.3.2: don't call Append() as an extension method else ambiguous syntax error // with the new extension method in .NET Fx v4.7.1 / .NET Standard 2.0: System.Linq.Enumerable.Append() let cycle = ExtensionMethodsEnumerable.Append(usersAndUsed,suspect) |
We expect support on this within the next weeks and months when more and more users will run the .NET Fx v4.7.1 while not changing their rules-set. There is no lesson learnt, this situation can happen and it happens rarely, this shouldn’t prevent you from declaring and calling extension methods. The more mature the frameworks you are relying on, the less likely it’ll happen.