QueryKey

A structured, ordered cache key with value equality on a canonical form. Segment order matters; dictionary segments are normalised by sorting their keys.

public sealed class QueryKey : IEquatable<QueryKey>

Members#

Of#

public static QueryKey Of(params object?[] segments)

Creates a key from ordered segments: QueryKey.Of("todos", "detail", 5). Supported segment types: strings, numeric primitives, bool, char, Guid, DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan, enums, null, collections of these, string-keyed dictionaries, and any type implementing IQueryKeySegment. Throws ArgumentException for no segments or an unsupported segment type — failing loudly at the call site beats a silently colliding cache entry.

FromString#

public static QueryKey? FromString(string? key)

The named equivalent of the implicit string conversion. Null and whitespace-only input yields null — no key.

implicit operator#

public static implicit operator QueryKey?(string? key)

Keeps QueryKey = "todos" call sites compiling and meaning QueryKey.Of("todos"). Null or whitespace converts to null — no key.

StartsWith#

public bool StartsWith(QueryKey prefix)

True when this key's leading segments equal every segment of prefix["todos", 1] starts with ["todos"]. Compared segment-by-segment on canonical forms, never as a string prefix of the whole hash, so ["todo"] does not match ["todos"].

Equality#

public bool Equals(QueryKey? other) public static bool operator ==(QueryKey? left, QueryKey? right) public static bool operator !=(QueryKey? left, QueryKey? right) public override int GetHashCode()

Value equality on the canonical form.

ToString#

public override string ToString()

The canonical form, e.g. ["todos",{"page":2}].

Canonicalisation rules#

  • Strings are quoted and escaped, so ["a,b"] and ["a","b"] cannot collide.
  • Numbers, dates and times use the invariant culture.
  • Enums use their numeric value — a member can be renamed without silently changing cached identity.
  • Dictionaries must be string-keyed and are written with keys in ordinal order, so property order in an ad-hoc filter object doesn't change identity.
  • Collections nest, to a maximum depth of 32 (guards against cyclic graphs).
  • Computed by hand rather than with a JSON serializer, keeping the package dependency-free and trimming/AOT-safe.

IQueryKeySegment#

IQueryKeySegment#

public interface IQueryKeySegment { string ToKeySegment(); }

Lets a custom type participate as a key segment. ToKeySegment() must return a stable, deterministic string: equal values equal strings, distinct values distinct strings, across sessions — it participates directly in the key's identity. Deliberately an interface rather than reflection over arbitrary objects: key hashing runs on every cache lookup and must stay trimming- and AOT-safe. Anonymous types therefore cannot be segments — use a Dictionary<string, object?> or a named type implementing this interface.