Assemblies Referenced in Multiple Versions

Rule ND2802 .NET Framework Usage / System.Reflection

Detects third-party assemblies referenced in multiple versions, which can cause unexpected behavior and bugs. The issue explanation lists the application and third-party assemblies referencing the different versions; the scan is transitive.

How to fix

Reference each package in a single version via Transitive pinning in Central Package Management: set CentralPackageTransitivePinningEnabled to true in a Directory.Packages.props or Directory.Build.props import file. More: https://blog.ndepend.com/understand-nuget-central-package-management-and-transitive-pinning-with-examples/

Pinning aligns the versions your projects restore and compile against, but it cannot change a reference compiled into a third-party package: when the issue explanation lists a third-party assembly that uses the older version, the issue remains after pinning. Upgrade that package to a release built against the newer version, or remove it if your code barely uses it.

If pinning breaks the restore with a package downgrade error (NU1109), raise the central version of the package named in the error to a stable release that satisfies the graph, or leave pinning off. Never raise it to a prerelease.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Assemblies Referenced in Multiple Versions</Name>
// <Id>ND2802:AssembliesReferencedInMultipleVersions</Id>

warnif count > 0

// Use this hashset to avoid to have several issues on a type
let hashset = new HashSet<IType>()

// Build a lookup of all assembly references, 
// from both application and third-party assemblies used by the application,
// grouped by the referenced assembly name.
let refs = 
 (from asm in Assemblies
  from asmRef in asm.AssemblyReferences
  // Uncomment this line to run the rule on .NET assemblies too
  where !asmRef.Name.StartsWithAny("System.","Microsoft.")  
  select new { 
    asm,
    RefName = asmRef.Name,
    RefVersion = asmRef.Version })
  .ToLookup(x => x.RefName)

from refsForSameAsm in refs
let variousVersions = refsForSameAsm.Distinct(x => x.RefVersion).ToArray()

// Require multiple versions
where variousVersions.Length > 1 

// Resolve the IAssembly from the assembly reference name
let asm = Assemblies.FirstOrDefault(a => a.Name == refsForSameAsm.Key)
where asm != null

orderby asm.Name
orderby variousVersions.Length descending

// The issue appears on the first type using one culprit third-party assembly 
let typeUsers = ApplicationAndTestCode.Types.Using(asm)
    .Where(t => !t.IsGeneratedByCompiler || t.Name.StartsWith("Program+")).ToArray()
let FirstAppTypeUser = (typeUsers.Length == 0 ? ApplicationAndTestCode.Types : typeUsers ) // Make sure at least one type else we get an exception!
    .Aggregate((minElement, nextElement) => 
        string.Compare(nextElement.Name, minElement.Name, StringComparison.Ordinal) < 0 && !hashset.Contains(nextElement)
        ? nextElement 
        : minElement)
let fillHashset = hashset.Add(FirstAppTypeUser)

select new { 
  FirstAppTypeUser,
  asm,
  NbVersions = variousVersions.Length,
  Versions = variousVersions.Select(
     vv => "" + vv.asm.Name + " uses v" + vv.RefVersion.ToString() + "")
     .Aggregate("  ;  "),
  Severity = Severity.Medium,
  Debt = 15.ToMinutes().ToDebt()
}

//<Expl>
// The *{1}* is referenced through *{2}* versions. {3}.
//
// The *{0}* is the first type (alphabetically) in the application that uses this assembly.
//</Expl>

//<Desc>
// Detects third-party assemblies referenced in multiple versions, which can
// cause unexpected behavior and bugs. The issue explanation lists the
// application and third-party assemblies referencing the different
// versions; the scan is transitive.
//</Desc>

//<HowToFix>
// Reference each package in a single version via **Transitive pinning in
// Central Package Management**: set *CentralPackageTransitivePinningEnabled*
// to *true* in a *Directory.Packages.props* or *Directory.Build.props*
// import file. More:
// https://blog.ndepend.com/understand-nuget-central-package-management-and-transitive-pinning-with-examples/
//
// Pinning aligns the versions your projects restore and compile against,
// but it cannot change a reference compiled into a third-party package:
// when the issue explanation lists a third-party assembly that uses the
// older version, the issue remains after pinning. Upgrade that package to
// a release built against the newer version, or remove it if your code
// barely uses it.
//
// If pinning breaks the restore with a package downgrade error (NU1109),
// raise the central version of the package named in the error to a
// stable release that satisfies the graph, or leave pinning off. Never
// raise it to a prerelease.
//</HowToFix>

.NET Framework Usage / System.Reflection: all 3 items