Lightweight .NET library for calculating checksums and object fingerprints of any .NET object - from primitives and collections to arbitrary objects.
- Works with any object. Operates at the interface level (
IList,ISet,IDictionary, ...) rather than concrete implementations, and falls back to reflection for arbitrary objects. - Type-aware, value-stable checksums.
- Integer types with the same value share a checksum (e.g.
(int)5,(long)5,(byte)5). floatanddoubleof the same value share a checksum;Halfanddecimalare distinct.- Different families with the "same" value differ (e.g.
(int)5≠(float)5≠(decimal)5).
- Integer types with the same value share a checksum (e.g.
- Structure-aware composition.
- Ordered collections (arrays,
IList,IEnumerable) are order-sensitive. - Unordered collections (
ISet,IDictionary) are order-insensitive.
- Ordered collections (arrays,
- Customizable.
- Plug in your own hash function via
IHasher; the default is a fast, non-cryptographicXxHash128.XxHash3andCrc32hashers are also provided out of the box. - Implement
ISignumFingerprintableto control how a type contributes to its fingerprint. - Exclude individual members with
[SignumIgnore].
- Plug in your own hash function via
- Safe by default. Reference cycles are detected and handled gracefully.
$ dotnet add package Ddth.SignumUse the static Signum helper for the common case:
using Ddth.Signum;
byte[] checksum = Signum.Checksum(myObject);
string hex = Signum.ChecksumHex(myObject);Signum.Checksum accepts anything:
Signum.ChecksumHex(42); // primitives
Signum.ChecksumHex(new[] { 1, 2, 3 }); // ordered collections
Signum.ChecksumHex(new HashSet<int> { 1, 2, 3 }); // unordered collections
Signum.ChecksumHex(new { Name = "Alice", Age = 30 }); // arbitrary objectsSame value, same checksum - regardless of the concrete integer type or collection order:
Signum.ChecksumHex(5) == Signum.ChecksumHex(5L); // true (integer family)
var a = new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 };
var b = new Dictionary<string, int> { ["b"] = 2, ["a"] = 1 };
Signum.ChecksumHex(a) == Signum.ChecksumHex(b); // true (order-insensitive)
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 2, 1 };
Signum.ChecksumHex(list1) == Signum.ChecksumHex(list2); // false (order matters)The library ships three hashers (all from System.IO.Hashing): XxHash128Hasher (default,
16-byte), XxHash3Hasher (8-byte) and Crc32Hasher (4-byte). Select one via
FingerprintOptions.HasherFactory, or via the optional parameter on the Signum helper:
// Use a built-in hasher through the static helper. Each hasher exposes a shared
// `Factory` delegate, which lets the Signum helper cache and reuse a single
// Fingerprinter per hasher type:
string hex = Signum.ChecksumHex(myObject, XxHash3Hasher.Factory);Provide your own IHasher to use any other algorithm:
using System.IO.Hashing;
using Ddth.Signum;
public sealed class Crc64Hasher : IHasher
{
private readonly Crc64 _inner = new();
public int HashLengthInBytes => _inner.HashLengthInBytes;
public void Append(ReadOnlySpan<byte> data) => _inner.Append(data);
public void GetHashAndReset(Span<byte> destination) => _inner.GetHashAndReset(destination);
}
var fingerprinter = new Fingerprinter(new FingerprintOptions
{
HasherFactory = () => new Crc64Hasher()
});
byte[] checksum = fingerprinter.Compute(myObject);By default, an unknown object is fingerprinted over its public properties and fields. Exclude a
member with [SignumIgnore]:
public sealed class User
{
public string Name { get; set; }
[SignumIgnore]
public DateTime LastAccessed { get; set; } // ignored in the fingerprint
}For full control, implement ISignumFingerprintable:
public sealed class Money : ISignumFingerprintable
{
public decimal Amount { get; set; }
public string Currency { get; set; }
public void WriteFingerprint(IFingerprintWriter writer)
{
writer.Write(Amount).Write(Currency);
}
}This package is licensed under the MIT License - see the LICENSE.md file for details.
Feel free to create pull requests or issues to report bugs or suggest new features.