Skip to content

netstandard2.0 and .NET assemblies have incompatible public APIs #1735

Description

@tmilnthorp

Problem

The netstandard2.0 and .NET (net8.0+) assemblies in the UnitsNet 6 package expose incompatible public APIs. Some public members exist only in the netstandard2.0 assembly.

A library that targets netstandard2.0 compiles against the netstandard2.0 assembly. When an app targeting .NET 8+ uses that library, NuGet gives the app the .NET assembly, so the library's calls to those members fail at runtime with MissingMethodException.

Apps that target .NET directly aren't affected, since they compile and run against the same assembly. Only netstandard2.0 libraries used from .NET apps are, which is probably why nobody has reported it yet. It would surface once libraries start building on v6.

Repro

A library targeting netstandard2.0 that references UnitsNet:

public static class LibCode
{
    public static string GetQuantityName<TQuantity>(TQuantity quantity) where TQuantity : IQuantityOfType<TQuantity>
        => quantity.QuantityInfo.Name;

    public static string GetScalingFactor<TQuantity>(TQuantity quantity) where TQuantity : ILogarithmicQuantity<TQuantity>
        => quantity.LogarithmicScalingFactor.ToString();

    public static bool AreClose(Temperature a, Temperature b)
        => a.Equals(b, TemperatureDelta.FromKelvins(1));
}

A net10.0 app that references the library and UnitsNet, calling each method, built from master (3bf1ef4):

IQuantityOfType<T>.QuantityInfo: MissingMethodException: Method not found: 'UnitsNet.IQuantityInstanceInfo`1<!0> UnitsNet.IQuantityOfType`1.get_QuantityInfo()'.
ILogarithmicQuantity<T>.LogarithmicScalingFactor: MissingMethodException: Method not found: 'UnitsNet.QuantityValue UnitsNet.ILogarithmicQuantity`1.get_LogarithmicScalingFactor()'.
Temperature.Equals(other, tolerance): MissingMethodException: Method not found: 'Boolean UnitsNet.AffineQuantityExtensions.Equals(UnitsNet.Temperature, UnitsNet.Temperature, UnitsNet.TemperatureDelta)'.

Incompatible APIs

Turning on package validation for UnitsNet reports these as present in netstandard2.0 but missing from net8.0:

  • IQuantityInfo: public in netstandard2.0, internal on .NET
  • IQuantityInstanceInfo<TQuantity>: public in netstandard2.0, internal on .NET
  • IQuantityOfType<TQuantity>.QuantityInfo
  • AffineQuantityExtensions.Equals(this Temperature, Temperature, TemperatureDelta)
  • AffineQuantityExtensions.Equals(this Temperature, IQuantity?, TemperatureDelta)

Package validation misses one more, because it matches members by name and doesn't notice a change from instance to static:

  • ILogarithmicQuantity<TSelf>.LogarithmicScalingFactor: an instance property in netstandard2.0, a static abstract property on .NET

It also reports the static abstract members (Info, Create, From, Zero) and IAdditiveIdentity constraints that exist only on .NET. Those require .NET 7+ and can't exist on netstandard2.0, so they're expected.

Proposal

  • Make every public member available on netstandard2.0 exist on all target frameworks. Members that are only there for netstandard2.0 can be [Obsolete] on .NET, as IQuantity.QuantityInfo already is.
  • Turn on package validation for UnitsNet, as UnitsNet.Modular already does, so CI catches future differences. Suppress the static abstract additions with an explanation.
  • Decide how to handle LogarithmicScalingFactor. An interface can't have a static and an instance member with the same name, so either:
    1. remove the instance member from netstandard2.0, which breaks generic netstandard2.0 code over ILogarithmicQuantity<T>; or
    2. add an instance member under a different name on all target frameworks, implemented on .NET as a default interface member that returns TSelf.LogarithmicScalingFactor.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions