ILLightenComparer is a flexible library that can generate very effective and comprehensive IComparer<T>
and IEqualityComparer<T>
implementations on runtime using advantages of IL
code emission.
- Support for classes and structures any complexity and nesting.
- Highly configurable.
- Fluent intuitive API.
- Cycle detection.
- Collections comparison (
IEnumerable<T>
,T[]
,T[][]
). - .NET Standard 2.0, .NET 5.
- High performance.
- No 3rd party dependencies.
- Cycle detection.
- Ignoring collections order.
- Ignoring members.
- Including fields into comparison.
- Defining order in which members will be compared.
- Defining string comparison type.
- Defining custom comparers by type or instance.
var comparer = ComparerBuilder.Default.GetComparer<Tuple<int, string>>();
var compareResult = comparer.Compare(x, y);
var equalityComparer = ComparerBuilder.Default.GetEqualityComparer<Tuple<int, string>>();
var equalityResult = equalityComparer.Equals(x, y);
var hashResult = equalityComparer.GetHashCode(x);
And it "just works", no need complex configuration.
var x = new[] { 1, 2, 3 };
var y = new[] { 2, 3, 1 };
var comparer = new ComparerBuilder()
.For<int[]>(c => c.IgnoreCollectionsOrder(true))
.GetComparer();
var result = comparer.Compare(x, y);
result.Should().Be(0);
var x = new Tuple<int, string, double>(1, "value 1", 1.1);
var y = new Tuple<int, string, double>(1, "value 2", 2.2);
var comparer = new ComparerBuilder()
.For<Tuple<int, string, double>>()
.Configure(c => c.IgnoreMember(o => o.Item2)
.IgnoreMember(o => o.Item3))
.GetComparer();
var result = comparer.Compare(x, y);
result.Should().Be(0);
var x = _fixture.Create<Tuple<int, string>>();
var y = _fixture.Create<Tuple<int, string>>();
var customComparer = new CustomizableComparer<Tuple<int, string>>((a, b) => 0); // makes all objects always equal
var comparer = new ComparerBuilder()
.Configure(c => c.SetCustomComparer(customComparer))
.GetComparer<Tuple<int, string>>();
var result = comparer.Compare(x, y);
result.Should().Be(0);
var builder = new ComparerBuilder(c => c.SetDefaultCyclesDetection(false)); // defines initial configuration
// adds some configuration later
builder.Configure(c => c
.SetStringComparisonType(
typeof(Tuple<int, string, Tuple<short, string>>),
StringComparison.InvariantCultureIgnoreCase)
.IgnoreMember<Tuple<int, string, Tuple<short, string>>, int>(o => o.Item1));
// defines configuration for specific types
builder.For<Tuple<short, string>>(c => c.DefineMembersOrder(
order => order.Member(o => o.Item2)
.Member(o => o.Item2)));
// adds additional configuration to existing configuration
builder.For<Tuple<int, string, Tuple<short, string>>>(c => c.IncludeFields(false));
- Configuration is fixed for generated comparer. If you want to change configuration you have to request new comparer:
var x = new Tuple<int, string>(1, "text"); var y = new Tuple<int, string>(2, "TEXT"); // initially configuration defines case insensitive string comparison var builder = new ComparerBuilder() .For<Tuple<int, string>>(c => c .SetStringComparisonType(StringComparison.CurrentCultureIgnoreCase) .DetectCycles(false)); // in addition, setup to ignore first member builder.Configure(c => c.IgnoreMember(o => o.Item1)); // this version takes in account only case insensitive second member var ignoreCaseComparer = builder.GetComparer(); // override string comparison type with case sensitive setting and build new comparer var originalCaseComparer = builder .For<Tuple<int, string>>() .Configure(c => c.SetStringComparisonType(StringComparison.Ordinal)) .GetComparer(); // first comparer ignores case for strings still ignoreCaseComparer.Compare(x, y).Should().Be(0); // second comparer still ignores first member but uses new string comparison type var result = originalCaseComparer.Compare(x, y); result.Should().Be(string.Compare("text", "TEXT", StringComparison.Ordinal));
- To help generate more effective code use
sealed
classes and small types (sbyte
,byte
,char
,short
,ushort
) when possible. - For safety reasons cycle detection is enabled by default. But when you are sure that it is not possible you can disable it and get significant performance boost.
- protected and private members are ignored during comparison.
- Multidimensional arrays are not supported now, but Jagged arrays are.
- If a type implements
IComparable<T>
interface then this implementations will be used. - Benchmarks
In case of an unexpected behavior, please welcome to create an issue and provide the type and data that you use.