Skip to content

Commit 3533334

Browse files
benaadamsrynowak
authored andcommitted
Allow non-boxing equality for StringValues
1 parent 52762b0 commit 3533334

File tree

2 files changed

+325
-6
lines changed

2 files changed

+325
-6
lines changed

src/Microsoft.Extensions.Primitives/StringValues.cs

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7+
using Microsoft.Extensions.Internal;
78

89
namespace Microsoft.Extensions.Primitives
910
{
1011
/// <summary>
1112
/// Represents zero/null, one, or many strings in an efficient way.
1213
/// </summary>
13-
public struct StringValues : IList<string>, IReadOnlyList<string>
14+
public struct StringValues : IList<string>, IReadOnlyList<string>, IEquatable<StringValues>, IEquatable<string>, IEquatable<string[]>
1415
{
1516
private static readonly string[] EmptyArray = new string[0];
1617
public static readonly StringValues Empty = new StringValues(EmptyArray);
@@ -253,6 +254,170 @@ public static StringValues Concat(StringValues values1, StringValues values2)
253254
return new StringValues(combined);
254255
}
255256

257+
public static bool Equals(StringValues left, StringValues right)
258+
{
259+
var count = left.Count;
260+
261+
if (count != right.Count)
262+
{
263+
return false;
264+
}
265+
266+
for (var i = 0; i < count; i++)
267+
{
268+
if (left[i] != right[i])
269+
{
270+
return false;
271+
}
272+
}
273+
274+
return true;
275+
}
276+
277+
public static bool operator ==(StringValues left, StringValues right)
278+
{
279+
return Equals(left, right);
280+
}
281+
282+
public static bool operator !=(StringValues left, StringValues right)
283+
{
284+
return !Equals(left, right);
285+
}
286+
287+
public bool Equals(StringValues other)
288+
{
289+
return Equals(this, other);
290+
}
291+
292+
public static bool Equals(string left, StringValues right)
293+
{
294+
return Equals(new StringValues(left), right);
295+
}
296+
297+
public static bool Equals(StringValues left, string right)
298+
{
299+
return Equals(left, new StringValues(right));
300+
}
301+
302+
public bool Equals(string other)
303+
{
304+
return Equals(this, new StringValues(other));
305+
}
306+
307+
public static bool Equals(string[] left, StringValues right)
308+
{
309+
return Equals(new StringValues(left), right);
310+
}
311+
312+
public static bool Equals(StringValues left, string[] right)
313+
{
314+
return Equals(left, new StringValues(right));
315+
}
316+
317+
public bool Equals(string[] other)
318+
{
319+
return Equals(this, new StringValues(other));
320+
}
321+
322+
public static bool operator ==(StringValues left, string right)
323+
{
324+
return Equals(left, new StringValues(right));
325+
}
326+
327+
public static bool operator !=(StringValues left, string right)
328+
{
329+
return !Equals(left, new StringValues(right));
330+
}
331+
332+
public static bool operator ==(string left, StringValues right)
333+
{
334+
return Equals(new StringValues(left), right);
335+
}
336+
337+
public static bool operator !=(string left, StringValues right)
338+
{
339+
return !Equals(new StringValues(left), right);
340+
}
341+
342+
public static bool operator ==(StringValues left, string[] right)
343+
{
344+
return Equals(left, new StringValues(right));
345+
}
346+
347+
public static bool operator !=(StringValues left, string[] right)
348+
{
349+
return !Equals(left, new StringValues(right));
350+
}
351+
352+
public static bool operator ==(string[] left, StringValues right)
353+
{
354+
return Equals(new StringValues(left), right);
355+
}
356+
357+
public static bool operator !=(string[] left, StringValues right)
358+
{
359+
return !Equals(new StringValues(left), right);
360+
}
361+
362+
public static bool operator ==(StringValues left, object right)
363+
{
364+
return left.Equals(right);
365+
}
366+
367+
public static bool operator !=(StringValues left, object right)
368+
{
369+
return !left.Equals(right);
370+
}
371+
public static bool operator ==(object left, StringValues right)
372+
{
373+
return right.Equals(left);
374+
}
375+
376+
public static bool operator !=(object left, StringValues right)
377+
{
378+
return !right.Equals(left);
379+
}
380+
381+
public override bool Equals(object obj)
382+
{
383+
if (obj == null)
384+
{
385+
return Equals(this, StringValues.Empty);
386+
}
387+
388+
if (obj is string)
389+
{
390+
return Equals(this, (string)obj);
391+
}
392+
393+
if (obj is string[])
394+
{
395+
return Equals(this, (string[])obj);
396+
}
397+
398+
if (obj is StringValues)
399+
{
400+
return Equals(this, (StringValues)obj);
401+
}
402+
403+
return false;
404+
}
405+
406+
public override int GetHashCode()
407+
{
408+
if (_values == null)
409+
{
410+
return _value == null ? 0 : _value.GetHashCode();
411+
}
412+
413+
var hcc = new HashCodeCombiner();
414+
for (var i = 0; i < _values.Length; i++)
415+
{
416+
hcc.Add(_values[i]);
417+
}
418+
return hcc.CombinedHash;
419+
}
420+
256421
public struct Enumerator : IEnumerator<string>
257422
{
258423
private readonly StringValues _values;

test/Microsoft.Extensions.Primitives.Tests/StringValuesTests.cs

Lines changed: 159 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,53 @@ public static TheoryData<StringValues> FilledStringValues
5757
}
5858
}
5959

60+
public static TheoryData<StringValues, string> FilledStringValuesWithExpectedStrings
61+
{
62+
get
63+
{
64+
return new TheoryData<StringValues, string>
65+
{
66+
{ default(StringValues), (string)null },
67+
{ StringValues.Empty, (string)null },
68+
{ new StringValues(new string[] { }), (string)null },
69+
{ new StringValues(string.Empty), string.Empty },
70+
{ new StringValues(new string[] { string.Empty }), string.Empty },
71+
{ new StringValues("abc"), "abc" }
72+
};
73+
}
74+
}
75+
76+
public static TheoryData<StringValues, object> FilledStringValuesWithExpectedObjects
77+
{
78+
get
79+
{
80+
return new TheoryData<StringValues, object>
81+
{
82+
{ default(StringValues), (object)null },
83+
{ StringValues.Empty, (object)null },
84+
{ new StringValues(new string[] { }), (object)null },
85+
{ new StringValues("abc"), (object)"abc" },
86+
{ new StringValues("abc"), (object)new[] { "abc" } },
87+
{ new StringValues(new[] { "abc" }), (object)new[] { "abc" } },
88+
{ new StringValues(new[] { "abc", "bcd" }), (object)new[] { "abc", "bcd" } }
89+
};
90+
}
91+
}
92+
6093
public static TheoryData<StringValues, string[]> FilledStringValuesWithExpected
6194
{
6295
get
6396
{
6497
return new TheoryData<StringValues, string[]>
6598
{
99+
{ default(StringValues), new string[0] },
100+
{ StringValues.Empty, new string[0] },
101+
{ new StringValues(string.Empty), new[] { string.Empty } },
66102
{ new StringValues("abc"), new[] { "abc" } },
67103
{ new StringValues(new[] { "abc" }), new[] { "abc" } },
68104
{ new StringValues(new[] { "abc", "bcd" }), new[] { "abc", "bcd" } },
69105
{ new StringValues(new[] { "abc", "bcd", "foo" }), new[] { "abc", "bcd", "foo" } },
106+
{ string.Empty, new[] { string.Empty } },
70107
{ "abc", new[] { "abc" } },
71108
{ new[] { "abc" }, new[] { "abc" } },
72109
{ new[] { "abc", "bcd" }, new[] { "abc", "bcd" } },
@@ -260,16 +297,29 @@ public void Contains(StringValues stringValues, string[] expected)
260297

261298
[Theory]
262299
[MemberData(nameof(FilledStringValuesWithExpected))]
263-
public void CopyTo(StringValues stringValues, string[] expected)
300+
public void CopyTo_TooSmall(StringValues stringValues, string[] expected)
264301
{
265302
ICollection<string> collection = stringValues;
266-
267303
string[] tooSmall = new string[0];
268-
Assert.Throws<ArgumentException>(() => collection.CopyTo(tooSmall, 0));
269304

305+
if (collection.Count > 0)
306+
{
307+
Assert.Throws<ArgumentException>(() => collection.CopyTo(tooSmall, 0));
308+
}
309+
}
310+
311+
[Theory]
312+
[MemberData(nameof(FilledStringValuesWithExpected))]
313+
public void CopyTo_CorrectSize(StringValues stringValues, string[] expected)
314+
{
315+
ICollection<string> collection = stringValues;
270316
string[] actual = new string[expected.Length];
271-
Assert.Throws<ArgumentOutOfRangeException>(() => collection.CopyTo(actual, -1));
272-
Assert.Throws<ArgumentException>(() => collection.CopyTo(actual, actual.Length + 1));
317+
318+
if (collection.Count > 0)
319+
{
320+
Assert.Throws<ArgumentOutOfRangeException>(() => collection.CopyTo(actual, -1));
321+
Assert.Throws<ArgumentException>(() => collection.CopyTo(actual, actual.Length + 1));
322+
}
273323
collection.CopyTo(actual, 0);
274324
Assert.Equal(expected, actual);
275325
}
@@ -302,5 +352,109 @@ public void Concat(StringValues stringValues, string[] array)
302352
string[] expectedAppended = filled.Concat(array).ToArray();
303353
Assert.Equal(expectedAppended, StringValues.Concat(new StringValues(filled), stringValues));
304354
}
355+
356+
[Fact]
357+
public void Equals_OperatorEqual()
358+
{
359+
var equalString = "abc";
360+
361+
var equalStringArray = new string[] { equalString };
362+
var equalStringValues = new StringValues(equalString);
363+
var stringArray = new string[] { equalString, equalString };
364+
var stringValuesArray = new StringValues(stringArray);
365+
366+
Assert.True(equalStringValues == equalStringValues);
367+
368+
Assert.True(equalStringValues == equalString);
369+
Assert.True(equalString == equalStringValues);
370+
371+
Assert.True(equalStringValues == equalStringArray);
372+
Assert.True(equalStringArray == equalStringValues);
373+
374+
Assert.True(stringArray == stringValuesArray);
375+
Assert.True(stringValuesArray == stringArray);
376+
377+
Assert.False(stringValuesArray == equalString);
378+
Assert.False(stringValuesArray == equalStringArray);
379+
Assert.False(stringValuesArray == equalStringValues);
380+
}
381+
382+
[Fact]
383+
public void Equals_OperatorNotEqual()
384+
{
385+
var equalString = "abc";
386+
387+
var equalStringArray = new string[] { equalString };
388+
var equalStringValues = new StringValues(equalString);
389+
var stringArray = new string[] { equalString, equalString };
390+
var stringValuesArray = new StringValues(stringArray);
391+
392+
Assert.False(equalStringValues != equalStringValues);
393+
394+
Assert.False(equalStringValues != equalString);
395+
Assert.False(equalString != equalStringValues);
396+
397+
Assert.False(equalStringValues != equalStringArray);
398+
Assert.False(equalStringArray != equalStringValues);
399+
400+
Assert.False(stringArray != stringValuesArray);
401+
Assert.False(stringValuesArray != stringArray);
402+
403+
Assert.True(stringValuesArray != equalString);
404+
Assert.True(stringValuesArray != equalStringArray);
405+
Assert.True(stringValuesArray != equalStringValues);
406+
}
407+
408+
[Fact]
409+
public void Equals_Instance()
410+
{
411+
var equalString = "abc";
412+
413+
var equalStringArray = new string[] { equalString };
414+
var equalStringValues = new StringValues(equalString);
415+
var stringArray = new string[] { equalString, equalString };
416+
var stringValuesArray = new StringValues(stringArray);
417+
418+
Assert.True(equalStringValues.Equals(equalStringValues));
419+
Assert.True(equalStringValues.Equals(equalString));
420+
Assert.True(equalStringValues.Equals(equalStringArray));
421+
Assert.True(stringValuesArray.Equals(stringArray));
422+
}
423+
424+
[Theory]
425+
[MemberData(nameof(FilledStringValuesWithExpectedObjects))]
426+
public void Equals_ObjectEquals(StringValues stringValues, object obj)
427+
{
428+
Assert.True(stringValues == obj);
429+
Assert.True(obj == stringValues);
430+
}
431+
432+
[Theory]
433+
[MemberData(nameof(FilledStringValuesWithExpectedObjects))]
434+
public void Equals_ObjectNotEquals(StringValues stringValues, object obj)
435+
{
436+
Assert.False(stringValues != obj);
437+
Assert.False(obj != stringValues);
438+
}
439+
440+
[Theory]
441+
[MemberData(nameof(FilledStringValuesWithExpectedStrings))]
442+
public void Equals_String(StringValues stringValues, string expected)
443+
{
444+
var notEqual = new StringValues("bcd");
445+
446+
Assert.True(StringValues.Equals(stringValues, expected));
447+
Assert.False(StringValues.Equals(stringValues, notEqual));
448+
}
449+
450+
[Theory]
451+
[MemberData(nameof(FilledStringValuesWithExpected))]
452+
public void Equals_StringArray(StringValues stringValues, string[] expected)
453+
{
454+
var notEqual = new StringValues(new[] { "bcd", "abc" });
455+
456+
Assert.True(StringValues.Equals(stringValues, expected));
457+
Assert.False(StringValues.Equals(stringValues, notEqual));
458+
}
305459
}
306460
}

0 commit comments

Comments
 (0)