Skip to content

Commit f32a2e7

Browse files
authored
IComparable for complex params (#2304)
* Add test for IComparable * Detect IComparable params and use that for ordering * Create IntroComparableComplexParam.cs * Add IntroComparableComplexParam sample doc * Remove primitive comparers in favor of IComparable * Simplify intro example * Simplify more
1 parent 492e58e commit f32a2e7

File tree

5 files changed

+145
-29
lines changed

5 files changed

+145
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
uid: BenchmarkDotNet.Samples.IntroComparableComplexParam
3+
---
4+
5+
## Sample: IntroComparableComplexParam
6+
7+
You can implement `IComparable` (the non generic version) on your complex parameter class if you want custom ordering behavior for your parameter.
8+
9+
One use case for this is having a parameter class that overrides `ToString()`, but also providing a custom ordering behavior that isn't the alphabetical order of the result of `ToString()`.
10+
11+
### Source code
12+
13+
[!code-csharp[IntroComparableComplexParam.cs](../../../samples/BenchmarkDotNet.Samples/IntroComparableComplexParam.cs)]
14+
15+
### Links
16+
17+
* @docs.parameterization
18+
* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroComparableComplexParam
19+
20+
---

docs/articles/samples/toc.yml

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
href: IntroCategoryBaseline.md
1515
- name: IntroColdStart
1616
href: IntroColdStart.md
17+
- name: IntroComparableComplexParam
18+
href: IntroComparableComplexParam.md
1719
- name: IntroConfigSource
1820
href: IntroConfigSource.md
1921
- name: IntroConfigUnion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using BenchmarkDotNet.Attributes;
4+
5+
namespace BenchmarkDotNet.Samples
6+
{
7+
public class IntroComparableComplexParam
8+
{
9+
[ParamsSource(nameof(ValuesForA))]
10+
public ComplexParam A { get; set; }
11+
12+
public IEnumerable<ComplexParam> ValuesForA => new[] { new ComplexParam(1, "First"), new ComplexParam(2, "Second") };
13+
14+
[Benchmark]
15+
public object Benchmark() => A;
16+
17+
// Only non generic IComparable is required to provide custom order behavior, but implementing IComparable<> too is customary.
18+
public class ComplexParam : IComparable<ComplexParam>, IComparable
19+
{
20+
public ComplexParam(int value, string name)
21+
{
22+
Value = value;
23+
Name = name;
24+
}
25+
26+
public int Value { get; set; }
27+
28+
public string Name { get; set; }
29+
30+
public override string ToString() => Name;
31+
32+
public int CompareTo(ComplexParam other) => other == null ? 1 : Value.CompareTo(other.Value);
33+
34+
public int CompareTo(object obj) => obj is ComplexParam other ? CompareTo(other) : throw new ArgumentException();
35+
}
36+
}
37+
}

src/BenchmarkDotNet/Parameters/ParameterComparer.cs

+9-29
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,32 @@ internal class ParameterComparer : IComparer<ParameterInstances>
77
{
88
public static readonly ParameterComparer Instance = new ParameterComparer();
99

10-
// We will only worry about common, basic types, i.e. int, long, double, etc
11-
// (e.g. you can't write [Params(10.0m, 20.0m, 100.0m, 200.0m)], the compiler won't let you!)
12-
private static readonly Comparer PrimitiveComparer = new Comparer().
13-
Add((string x, string y) => string.CompareOrdinal(x, y)).
14-
Add((int x, int y) => x.CompareTo(y)).
15-
Add((long x, long y) => x.CompareTo(y)).
16-
Add((short x, short y) => x.CompareTo(y)).
17-
Add((float x, float y) => x.CompareTo(y)).
18-
Add((double x, double y) => x.CompareTo(y));
19-
2010
public int Compare(ParameterInstances x, ParameterInstances y)
2111
{
2212
if (x == null && y == null) return 0;
2313
if (x != null && y == null) return 1;
2414
if (x == null) return -1;
2515
for (int i = 0; i < Math.Min(x.Count, y.Count); i++)
2616
{
27-
int compareTo = PrimitiveComparer.CompareTo(x[i]?.Value, y[i]?.Value);
17+
var compareTo = CompareValues(x[i]?.Value, y[i]?.Value);
2818
if (compareTo != 0)
2919
return compareTo;
3020
}
3121
return string.CompareOrdinal(x.DisplayInfo, y.DisplayInfo);
3222
}
3323

34-
private class Comparer
24+
private int CompareValues(object x, object y)
3525
{
36-
private readonly Dictionary<Type, Func<object, object, int>> comparers =
37-
new Dictionary<Type, Func<object, object, int>>();
38-
39-
public Comparer Add<T>(Func<T, T, int> compareFunc)
40-
{
41-
comparers.Add(typeof(T), (x, y) => compareFunc((T)x, (T)y));
42-
return this;
43-
}
44-
45-
public int CompareTo(object x, object y)
26+
// Detect IComparable implementations.
27+
// This works for all primitive types in addition to user types that implement IComparable.
28+
if (x != null && y != null && x.GetType() == y.GetType() &&
29+
x is IComparable xComparable)
4630
{
47-
return x != null && y != null && x.GetType() == y.GetType() && comparers.TryGetValue(GetComparisonType(x), out var comparer)
48-
? comparer(x, y)
49-
: string.CompareOrdinal(x?.ToString(), y?.ToString());
31+
return xComparable.CompareTo(y);
5032
}
5133

52-
private static Type GetComparisonType(object x) =>
53-
x.GetType().IsEnum
54-
? x.GetType().GetEnumUnderlyingType()
55-
: x.GetType();
34+
// Anything else.
35+
return string.CompareOrdinal(x?.ToString(), y?.ToString());
5636
}
5737
}
5838
}

tests/BenchmarkDotNet.Tests/ParameterComparerTests.cs

+77
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,82 @@ public void AlphaNumericComparisionTest()
122122
Assert.Equal(1000, sortedData[2].Items[0].Value);
123123
Assert.Equal(2000, sortedData[3].Items[0].Value);
124124
}
125+
126+
[Fact]
127+
public void IComparableComparisionTest()
128+
{
129+
var comparer = ParameterComparer.Instance;
130+
131+
var originalData = new[]
132+
{
133+
new ParameterInstances(new[]
134+
{
135+
new ParameterInstance(sharedDefinition, new ComplexParameter(1, "first"), null)
136+
}),
137+
new ParameterInstances(new[]
138+
{
139+
new ParameterInstance(sharedDefinition, new ComplexParameter(3, "third"), null)
140+
}),
141+
new ParameterInstances(new[]
142+
{
143+
new ParameterInstance(sharedDefinition, new ComplexParameter(2, "second"), null)
144+
}),
145+
new ParameterInstances(new[]
146+
{
147+
new ParameterInstance(sharedDefinition, new ComplexParameter(4, "fourth"), null)
148+
})
149+
};
150+
151+
var sortedData = originalData.OrderBy(d => d, comparer).ToArray();
152+
153+
// Check that we sort by numeric value, not string order!!
154+
Assert.Equal(1, ((ComplexParameter)sortedData[0].Items[0].Value).Value);
155+
Assert.Equal(2, ((ComplexParameter)sortedData[1].Items[0].Value).Value);
156+
Assert.Equal(3, ((ComplexParameter)sortedData[2].Items[0].Value).Value);
157+
Assert.Equal(4, ((ComplexParameter)sortedData[3].Items[0].Value).Value);
158+
}
159+
160+
private class ComplexParameter : IComparable<ComplexParameter>, IComparable
161+
{
162+
public ComplexParameter(int value, string name)
163+
{
164+
Value = value;
165+
Name = name;
166+
}
167+
168+
public int Value { get; }
169+
170+
public string Name { get; }
171+
172+
public override string ToString()
173+
{
174+
return Name;
175+
}
176+
177+
public int CompareTo(ComplexParameter other)
178+
{
179+
if (other == null)
180+
{
181+
return 1;
182+
}
183+
184+
return Value.CompareTo(other.Value);
185+
}
186+
187+
public int CompareTo(object obj)
188+
{
189+
if (obj == null)
190+
{
191+
return 1;
192+
}
193+
194+
if (obj is not ComplexParameter other)
195+
{
196+
throw new ArgumentException();
197+
}
198+
199+
return CompareTo(other);
200+
}
201+
}
125202
}
126203
}

0 commit comments

Comments
 (0)