Skip to content

Commit fde098d

Browse files
authored
OneOrMany implementation (#507)
* Improve OneOrMany implementation. * Add tests. * Throw for null comparer.
1 parent acbd678 commit fde098d

2 files changed

Lines changed: 168 additions & 3 deletions

File tree

src/Ardalis.Specification/Internals/OneOrMany.cs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Ardalis.Specification;
22

3-
internal struct OneOrMany<T>
3+
internal struct OneOrMany<T> where T : class
44
{
55
private object? _value;
66

@@ -24,8 +24,47 @@ public void Add(T item)
2424
if (_value is T singleValue)
2525
{
2626
_value = new List<T>(2) { singleValue, item };
27+
}
28+
}
29+
30+
public void AddSorted(T item, IComparer<T> comparer)
31+
{
32+
if (_value is null)
33+
{
34+
_value = item;
35+
return;
36+
}
37+
38+
if (comparer is null)
39+
{
40+
throw new ArgumentNullException(nameof(comparer), "Comparer cannot be null.");
41+
}
42+
43+
if (_value is List<T> list)
44+
{
45+
var index = list.FindIndex(x => comparer.Compare(item, x) <= 0);
46+
if (index == -1)
47+
{
48+
list.Add(item);
49+
}
50+
else
51+
{
52+
list.Insert(index, item);
53+
}
2754
return;
2855
}
56+
57+
if (_value is T singleValue)
58+
{
59+
if (comparer.Compare(item, singleValue) <= 0)
60+
{
61+
_value = new List<T>(2) { item, singleValue };
62+
}
63+
else
64+
{
65+
_value = new List<T>(2) { singleValue, item };
66+
}
67+
}
2968
}
3069

3170
public readonly T Single
@@ -41,6 +80,19 @@ public readonly T Single
4180
}
4281
}
4382

83+
public readonly T? SingleOrDefault
84+
{
85+
get
86+
{
87+
if (_value is T singleValue)
88+
{
89+
return singleValue;
90+
}
91+
92+
return null;
93+
}
94+
}
95+
4496
public readonly IEnumerable<T> Values
4597
{
4698
get
@@ -50,9 +102,9 @@ public readonly IEnumerable<T> Values
50102
return Enumerable.Empty<T>();
51103
}
52104

53-
if (_value is List<T> tags)
105+
if (_value is List<T> list)
54106
{
55-
return tags;
107+
return list;
56108
}
57109

58110
if (_value is T singleValue)

tests/Ardalis.Specification.Tests/Internals/OneOrManyTests.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,93 @@ public void Add_DoesNothing_GivenInvalidState()
9797
value.Should().BeEquivalentTo(new string[] { "foo", "bar" });
9898
}
9999

100+
[Fact]
101+
public void AddSorted_CreatesSingleItem_GivenEmptyStruct()
102+
{
103+
var oneOrMany = new OneOrMany<string>();
104+
oneOrMany.AddSorted("foo", Comparer<string>.Default);
105+
106+
var value = Accessors.ValueOf(ref oneOrMany);
107+
value.Should().BeOfType<string>();
108+
value.Should().Be("foo");
109+
}
110+
111+
[Fact]
112+
public void AddSorted_InsertsInPosition_GivenSingleItem()
113+
{
114+
var oneOrMany = new OneOrMany<string>();
115+
Accessors.ValueOf(ref oneOrMany) = "foo";
116+
117+
oneOrMany.AddSorted("bar", Comparer<string>.Default);
118+
119+
var value = Accessors.ValueOf(ref oneOrMany);
120+
value.Should().BeOfType<List<string>>();
121+
value.Should().BeEquivalentTo(new List<string> { "bar", "foo" });
122+
}
123+
124+
[Fact]
125+
public void AddSorted_AddsToTheEnd_GivenSingleItem()
126+
{
127+
var oneOrMany = new OneOrMany<string>();
128+
Accessors.ValueOf(ref oneOrMany) = "bar";
129+
130+
oneOrMany.AddSorted("foo", Comparer<string>.Default);
131+
132+
var value = Accessors.ValueOf(ref oneOrMany);
133+
value.Should().BeOfType<List<string>>();
134+
value.Should().BeEquivalentTo(new List<string> { "bar", "foo" });
135+
}
136+
137+
[Fact]
138+
public void AddSorted_InsertsInPosition_GivenTwoItems()
139+
{
140+
var oneOrMany = new OneOrMany<string>();
141+
Accessors.ValueOf(ref oneOrMany) = new List<string> { "bar", "foo" };
142+
143+
oneOrMany.AddSorted("baz", Comparer<string>.Default);
144+
145+
var value = Accessors.ValueOf(ref oneOrMany);
146+
value.Should().BeOfType<List<string>>();
147+
value.Should().BeEquivalentTo(new List<string> { "bar", "baz", "foo" });
148+
}
149+
150+
[Fact]
151+
public void AddSorted_AddsToTheEnd_GivenTwoItems()
152+
{
153+
var oneOrMany = new OneOrMany<string>();
154+
Accessors.ValueOf(ref oneOrMany) = new List<string> { "bar", "baz" };
155+
156+
oneOrMany.AddSorted("foo", Comparer<string>.Default);
157+
158+
var value = Accessors.ValueOf(ref oneOrMany);
159+
value.Should().BeOfType<List<string>>();
160+
value.Should().BeEquivalentTo(new List<string> { "bar", "baz", "foo" });
161+
}
162+
163+
[Fact]
164+
public void AddSorted_ThrowsArgumentNullException_GivenNullComparer()
165+
{
166+
var oneOrMany = new OneOrMany<string>();
167+
Accessors.ValueOf(ref oneOrMany) = new List<string> { "bar", "baz" };
168+
169+
var action = () => oneOrMany.AddSorted("foo", null!);
170+
171+
action.Should().Throw<ArgumentNullException>();
172+
}
173+
174+
[Fact]
175+
public void AddSorted_DoesNothing_GivenInvalidState()
176+
{
177+
var oneOrMany = new OneOrMany<string>();
178+
Accessors.ValueOf(ref oneOrMany) = new string[] { "foo", "bar" };
179+
180+
oneOrMany.AddSorted("baz", Comparer<string>.Default);
181+
182+
var value = Accessors.ValueOf(ref oneOrMany);
183+
value.Should().BeOfType<string[]>();
184+
value.Should().BeEquivalentTo(new string[] { "foo", "bar" });
185+
}
186+
100187
[Fact]
101188
public void Single_ReturnsSingleItem_GivenSingleItem()
102189
{
@@ -125,6 +212,32 @@ public void Single_Throws_GivenMultipleItems()
125212
action.Should().Throw<InvalidOperationException>();
126213
}
127214

215+
[Fact]
216+
public void SingleOrDefault_ReturnsSingleItem_GivenSingleItem()
217+
{
218+
var oneOrMany = new OneOrMany<string>();
219+
Accessors.ValueOf(ref oneOrMany) = "foo";
220+
221+
oneOrMany.SingleOrDefault.Should().Be("foo");
222+
}
223+
224+
[Fact]
225+
public void SingleOrDefault_ReturnsDefault_GivenEmptyStruct()
226+
{
227+
var oneOrMany = new OneOrMany<string>();
228+
229+
oneOrMany.SingleOrDefault.Should().BeNull();
230+
}
231+
232+
[Fact]
233+
public void SingleOrDefault_ReturnsDefault_GivenMultipleItems()
234+
{
235+
var oneOrMany = new OneOrMany<string>();
236+
Accessors.ValueOf(ref oneOrMany) = new string[] { "foo", "bar" };
237+
238+
oneOrMany.SingleOrDefault.Should().BeNull();
239+
}
240+
128241
[Fact]
129242
public void Values_ReturnsEmpty_GivenEmptyStruct()
130243
{

0 commit comments

Comments
 (0)