Yesterday evening I had an interesting discussion about the feasibility of migrating parts of the NDepend code to .NET Standard to ultimately run it on .NET Core. We’re not yet there but this might make sense to run at least the code analysis on non Windows platform, especially for NDepend clones CppDepend (for C++), JArchitect (for Java) and others to come.
Then I went to sleep (as every developers know the brain is coding hard while sleeping), then this morning I went for an early morning jogging and it struck me: NDepend is the perfect tool to assess some .NET code compliance to .NET Standard, or to any other libraries actually! As soon as I was on my machine, I did a proof of concept in less than an hour.
The key is that .NET standard 2.0 types and members are all packet in a single assemblies netstandard.dll v2.0 that can be found under C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll (on my machine). A quick analyze of netstandard.dll with NDepend shows 2 317 types in 78 namespaces, with 24 303 methods and 884 fields. Let’s precise that netstandard.dll doesn’t contain any code, it is a standard API definition not an implementation. The 68K IL instructions represent the IL code for throw null which is the method body for all non-abstract methods.
(Btw, I am sure that if you read this you have an understanding of what is .NET Standard but if anything is still unclear, I invite you to read this great article by my friend Laurent Bugnion A Brief History of .NET Standard)
Given that, what struck me this morning is that to analyze some .NET code compliance to .NET Standard, I’d just have to include netstandard.dll in the list of my application assemblies and write a code query that filters the dependencies the way I want. Of course to proof test this idea I wanted to explore the NDepend code base compliance to .NET Standard:
Important: to add netstandard.dll to the list of assemblies analyzed, please copy it first to its own directory like C:\temp\netstandard for example. The reason is that the aforementioned directory (C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref) that contains netstandard.dll also contains many System assemblies (like System.dll). These System assemblies are missing some .NET fx API. If you analyze netstandard.dll from this location NDepend will resolve some System assemblies in this directory and won’t report un-compliance about the missing API.
The code query was pretty straightforward to write. It is written in a way that:
- it is easy to use to analyze compliance with any other library than .NET standard,
- it is easy to explore the compliance and the non-compliance with a library in a comprehensive way, thanks to the NDepend code query result browsing facilities,
- it is easy to refactor the query for querying more, for example below I refactor it to assess the usage of third-party non .NET Standard compliant code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// <Name>Non-compliance with netstandard</Name> // asm is a sequence of application assemblies that contains elements to use // here asm contains only the netstandard assembly let asm =Application.Assemblies.WithNameIn("netstandard"/*, "anyotherlib1", "anyotherlib2"*/) // hashset contains all full names of netstandard types/methods/fields let hashset = asm.ChildTypesAndMembers().Select(x => x.FullName).ToHashSetEx() // iterate over all types/methods/fields of the application from c in Application.Assemblies.Except(asm).ChildTypesAndMembers() let nonNetStandardElemsUsed = c.IsType ? c.AsType.TypesUsed.Where (t => t.IsThirdParty && !hashset.Contains(t.FullName)).ToArray() : c.IsMethod ? c.AsMethod.MembersUsed.Where(m => m.IsThirdParty && !hashset.Contains(m.FullName)).ToArray() : new IMember[0] // Here c.IsField and a field is not using anything! let netStandardElemsUsed = c.IsType ? c.AsType.TypesUsed.Where (t => t.IsThirdParty && hashset.Contains(t.FullName)).ToArray() : c.IsMethod ? c.AsMethod.MembersUsed.Where(m => m.IsThirdParty && hashset.Contains(m.FullName)).ToArray() : new IMember[0] // Just comment or change this line to explore the compliance and/or non-compliance with netstandard where nonNetStandardElemsUsed.Any() select new { c, nonNetStandardElemsUsed, netStandardElemsUsed, loc = c.GetNbLinesOfCode_GuaranteedIfPDBFound() } |
The result looks like that and IMHO it is pretty interesting. For example we can see at a glance that NDepend.API is almost full compliant with .NET standard except for the usage of System.Drawing.Image (all the 1 type are the Image type actually) and for the usage of code contracts.
For a more intuitive assessment of the compliance to .NET Standard we can use the metric view, that highlights the code elements matched by the currently edited code query.
- Unsurprisingly NDepend.UI is not compliant at all,
- portions of NDepend.Core non compliant to .NET Standard are well defined (and I know it is mostly because of some UI code here too, that we consider Core because it is re-usable in a variety of situations).
With this information it’d be much easier to plan a major refactoring to segregate .NET standard compliant code from the non-compliant one, especially to anticipate hot spots that will be painful to refactor.
The code query to assess compliancy can be refactored at whim. For example I found it interesting to see which non-compliant third-party code elements were the most used. So I refactored the query this way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// ***** CODE COPY/PASTED FROM THE PREVIOUS QUERY *********** // asm is a sequence of application assemblies that contains elements to use // here asm contains only the netstandard assembly let asm =Application.Assemblies.WithNameIn("netstandard"/*, "anyotherlib1", "anyotherlib2"*/) // hashset contains all full names of netstandard types/methods/fields let hashset = asm.ChildTypesAndMembers().Select(x => x.FullName).ToHashSetEx() // *********************************************************** from c in ThirdParty.TypesAndMembers where !hashset.Contains(c.FullName) // We want third-party non compliant code elements let users = c.IsType ? c.AsType.TypesUsingMe.Cast<IMember>() : c.IsMethod ? c.AsMethod.MethodsCallingMe : c.AsField.MethodsUsingMe orderby users.Count() descending select new { c, users } |
Without surprise UI code that is non .NET Standard compliant popups first:
There is no limit to refactor this query to your own need, like assessing usage of non-compliant code — except UI code– for example, or assessing the usage of code non compliant to ASP.NET Core 2 (by changing the library).
Hope you’ll find this content useful to plan your migration to .NET Core and .NET Standard!