Table of Contents

Comparison & Precedence

SemVer implements full SemVer 2.0.0 precedence rules via IComparable<SemVer> and IComparisonOperators<SemVer, SemVer, bool>. Read this page when you sort versions or check whether one version is newer than another.


Precedence rules (summary)

The ordering follows the SemVer 2.0.0 specification §11:

  1. Major is compared numerically — higher major wins.
  2. Minor is compared numerically when major is equal.
  3. Patch is compared numerically when major and minor are equal.
  4. Release > Pre-release — when core versions are identical a release version (no pre-release) has higher precedence than any pre-release.
  5. Pre-release identifiers are compared dot-by-dot left-to-right:
    • If both identifiers consist only of digits they are compared numerically, whatever their length (10000000000 > 9999999999).
    • A numeric identifier always has lower precedence than an alphanumeric one (1.0.0-9 < 1.0.0-10a).
    • Two alphanumeric identifiers are compared lexically (ordinal, ASCII order).
    • A shorter identifier list is less than a longer one when all common identifiers are equal.
  6. Build metadata is not considered for precedence by the spec, but this implementation uses it as a final tiebreaker (ordinal comparison) when everything else is equal.

CompareTo

int result = SemVer.Parse("2.0.0").CompareTo(SemVer.Parse("1.0.0")); // > 0
int result = SemVer.Parse("1.0.0").CompareTo(SemVer.Parse("1.0.0")); // 0
int result = SemVer.Parse("1.0.0").CompareTo(SemVer.Parse("2.0.0")); // < 0

// null is always less than any instance
int result = SemVer.Parse("1.0.0").CompareTo(null); // > 0

This is compatible with List<T>.Sort(), Array.Sort(), SortedSet<T>, OrderBy, etc.


Comparison operators

All six standard comparison operators are supported:

SemVer a = "2.0.0";
SemVer b = "1.0.0";

bool gt  = a > b;   // true
bool gte = a >= b;  // true
bool lt  = a < b;   // false
bool lte = a <= b;  // false
bool eq  = a == b;  // false
bool neq = a != b;  // true

All six operators are null-safe. null sorts before every version, consistent with CompareTo, and two null operands are equal.

SemVer? x = null;
SemVer? y = null;
bool bothNull = (x == y); // true

SemVer? z = SemVer.Parse("1.0.0");
bool leftNull = (x == z);  // false
bool rightNull = (z == x); // false

bool lower = x < z;        // true — null precedes every version
bool higher = z > x;       // true
bool nullsEqual = x <= y;  // true

Ordering examples

Release beats pre-release

SemVer.Parse("1.0.0") > SemVer.Parse("1.0.0-alpha")   // true
SemVer.Parse("1.0.0-alpha") < SemVer.Parse("1.0.0")   // true

Numeric pre-release comparison

// 10 > 9 numerically (not "10" < "9" lexically)
SemVer.Parse("1.0.0-alpha.10") > SemVer.Parse("1.0.0-alpha.9"); // true

Numeric identifiers precede alphanumeric ones

// A purely numeric identifier always has lower precedence than an alphanumeric one
SemVer.Parse("1.0.0-alpha.1") < SemVer.Parse("1.0.0-alpha.beta"); // true
SemVer.Parse("1.0.0-9") < SemVer.Parse("1.0.0-10a");              // true

Lexicographic pre-release comparison

// 'b' > 'a' — beta is newer than alpha
SemVer.Parse("1.0.0-beta") > SemVer.Parse("1.0.0-alpha"); // true

Longer pre-release wins on tie

// "alpha.1.2" has more identifiers than "alpha.1"
SemVer.Parse("1.0.0-alpha.1.2") > SemVer.Parse("1.0.0-alpha.1"); // true

Build metadata as tiebreaker

// All version components and pre-release are equal — metadata breaks the tie
SemVer.Parse("1.0.0+build.2") > SemVer.Parse("1.0.0+build.1"); // true

Sorting a collection

var versions = new List<SemVer>
{
    "2.0.0",
    "1.0.0",
    "1.0.0-rc.1",
    "1.0.0-alpha",
    "1.0.0-alpha.1",
};

versions.Sort();
// Result (ascending): 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-rc.1, 1.0.0, 2.0.0

Equals and GetHashCode

Equals uses CompareTo under the hood, so equality respects SemVer rules (including metadata as tiebreaker):

SemVer.Parse("1.0.0").Equals(SemVer.Parse("1.0.0"));         // true
SemVer.Parse("1.0.0+a").Equals(SemVer.Parse("1.0.0+b"));     // false (metadata differs)
SemVer.Parse("1.0.0").Equals(SemVer.Parse("1.0.0-alpha"));    // false

GetHashCode is consistent with Equals — two equal SemVer instances always produce the same hash code.

See also