|  |  | 1 |  | // Copyright (c) Microsoft Corporation. All rights reserved. | 
|  |  | 2 |  | // Licensed under the MIT License. See License.txt in the project root for | 
|  |  | 3 |  | // license information. | 
|  |  | 4 |  |  | 
|  |  | 5 |  | using Microsoft.Azure.Search.Tests.Utilities; | 
|  |  | 6 |  | using System; | 
|  |  | 7 |  | using System.Collections; | 
|  |  | 8 |  | using System.Collections.Generic; | 
|  |  | 9 |  | using System.Linq; | 
|  |  | 10 |  | using Xunit; | 
|  |  | 11 |  |  | 
|  |  | 12 |  | namespace Microsoft.Azure.Search.Tests | 
|  |  | 13 |  | { | 
|  |  | 14 |  |     public sealed class ModelComparerTests | 
|  |  | 15 |  |     { | 
|  |  | 16 |  |         private enum Direction | 
|  |  | 17 |  |         { | 
|  |  | 18 |  |             Right, | 
|  |  | 19 |  |             Left | 
|  |  | 20 |  |         } | 
|  |  | 21 |  |  | 
|  |  | 22 |  |         [Fact] | 
|  |  | 23 |  |         public void CanComparePrimitives() | 
|  |  | 24 |  |         { | 
|  | 0 | 25 |  |             Assert.Equal(5, 5, new ModelComparer<int>()); | 
|  | 0 | 26 |  |             Assert.Equal(3.14, 3.14, new ModelComparer<double>()); | 
|  | 0 | 27 |  |             Assert.Equal('c', 'c', new ModelComparer<char>()); | 
|  | 0 | 28 |  |             Assert.Equal(5, 5L, new ModelComparer<object>()); | 
|  |  | 29 |  |  | 
|  | 0 | 30 |  |             Assert.NotEqual(5, 1, new ModelComparer<int>()); | 
|  | 0 | 31 |  |             Assert.NotEqual(3.14, -2.7, new ModelComparer<double>()); | 
|  | 0 | 32 |  |             Assert.NotEqual('c', 'z', new ModelComparer<char>()); | 
|  | 0 | 33 |  |             Assert.NotEqual(3, 3.0, new ModelComparer<object>()); | 
|  | 0 | 34 |  |         } | 
|  |  | 35 |  |  | 
|  |  | 36 |  |         [Fact] | 
|  |  | 37 |  |         public void CanCompareEnums() | 
|  |  | 38 |  |         { | 
|  | 0 | 39 |  |             Assert.Equal(Direction.Left, Direction.Left, new ModelComparer<Direction>()); | 
|  | 0 | 40 |  |             Assert.NotEqual(Direction.Left, Direction.Right, new ModelComparer<Direction>()); | 
|  | 0 | 41 |  |         } | 
|  |  | 42 |  |  | 
|  |  | 43 |  |         [Fact] | 
|  |  | 44 |  |         public void CanCompareNulls() | 
|  |  | 45 |  |         { | 
|  | 0 | 46 |  |             Model nullModel = null; | 
|  |  | 47 |  |  | 
|  | 0 | 48 |  |             Assert.Equal(nullModel, nullModel, new ModelComparer<Model>()); | 
|  | 0 | 49 |  |             Assert.NotEqual(new Model(), nullModel, new ModelComparer<Model>()); | 
|  | 0 | 50 |  |             Assert.NotEqual(nullModel, new Model(), new ModelComparer<Model>()); | 
|  |  | 51 |  |  | 
|  | 0 | 52 |  |             IEnumerable<int> nullCollection = null; | 
|  |  | 53 |  |  | 
|  | 0 | 54 |  |             Assert.True(new ModelComparer<IEnumerable<int>>().Equals(nullCollection, nullCollection)); | 
|  | 0 | 55 |  |             Assert.False(new ModelComparer<IEnumerable<int>>().Equals(new[] { 1 }, nullCollection)); | 
|  | 0 | 56 |  |             Assert.False(new ModelComparer<IEnumerable<int>>().Equals(nullCollection, new[] { 4, 5 })); | 
|  | 0 | 57 |  |         } | 
|  |  | 58 |  |  | 
|  |  | 59 |  |         [Fact] | 
|  |  | 60 |  |         public void CanCompareExtensibleEnums() | 
|  |  | 61 |  |         { | 
|  | 0 | 62 |  |             Assert.Equal(FancyDirection.Left, FancyDirection.Left, new ModelComparer<FancyDirection>()); | 
|  | 0 | 63 |  |             Assert.NotEqual(FancyDirection.Left, FancyDirection.Right, new ModelComparer<FancyDirection>()); | 
|  | 0 | 64 |  |         } | 
|  |  | 65 |  |  | 
|  |  | 66 |  |         [Fact] | 
|  |  | 67 |  |         public void CanCompareNullables() | 
|  |  | 68 |  |         { | 
|  | 0 | 69 |  |             int? maybeSeven = 7; | 
|  | 0 | 70 |  |             int? maybeFive = 5; | 
|  | 0 | 71 |  |             double? maybePi = 3.14; | 
|  | 0 | 72 |  |             double? maybeE = 2.718; | 
|  | 0 | 73 |  |             bool? maybeTrue = true; | 
|  | 0 | 74 |  |             bool? maybeFalse = false; | 
|  |  | 75 |  |  | 
|  | 0 | 76 |  |             Assert.Equal(maybeSeven, maybeSeven, new ModelComparer<int?>()); | 
|  | 0 | 77 |  |             Assert.Equal(maybePi, maybePi, new ModelComparer<double?>()); | 
|  | 0 | 78 |  |             Assert.Equal(maybeTrue, maybeTrue, new ModelComparer<bool?>()); | 
|  |  | 79 |  |  | 
|  | 0 | 80 |  |             Assert.NotEqual(maybeSeven, maybeFive, new ModelComparer<int?>()); | 
|  | 0 | 81 |  |             Assert.NotEqual(maybePi, maybeE, new ModelComparer<double?>()); | 
|  | 0 | 82 |  |             Assert.NotEqual(maybeTrue, maybeFalse, new ModelComparer<bool?>()); | 
|  |  | 83 |  |  | 
|  |  | 84 |  | #pragma warning disable xUnit2003 // Do not use equality check to test for null value | 
|  | 0 | 85 |  |             Assert.NotEqual(null, maybeSeven, new ModelComparer<int?>()); | 
|  | 0 | 86 |  |             Assert.NotEqual(null, maybePi, new ModelComparer<double?>()); | 
|  | 0 | 87 |  |             Assert.NotEqual(null, maybeTrue, new ModelComparer<bool?>()); | 
|  |  | 88 |  | #pragma warning restore xUnit2003 // Do not use equality check to test for null value | 
|  |  | 89 |  |  | 
|  |  | 90 |  | #pragma warning disable xUnit2000 // Literal or constant should be the first argument to Assert.NotEqual | 
|  | 0 | 91 |  |             Assert.NotEqual(maybeSeven, null, new ModelComparer<int?>()); | 
|  | 0 | 92 |  |             Assert.NotEqual(maybePi, null, new ModelComparer<double?>()); | 
|  | 0 | 93 |  |             Assert.NotEqual(maybeTrue, null, new ModelComparer<bool?>()); | 
|  |  | 94 |  | #pragma warning restore xUnit2000 // Literal or constant should be the first argument to Assert.NotEqual | 
|  | 0 | 95 |  |         } | 
|  |  | 96 |  |  | 
|  |  | 97 |  |         [Fact] | 
|  |  | 98 |  |         public void CanCompareCollections() | 
|  |  | 99 |  |         { | 
|  | 0 | 100 |  |             var ints = new[] { 1, 2, 3 }; | 
|  | 0 | 101 |  |             var otherInts = new[] { 4, 5, 6 }; | 
|  | 0 | 102 |  |             var longerInts = new[] { 1, 2, 3, 4 }; | 
|  | 0 | 103 |  |             var strings = new[] { "hello", "bonjour", "guten tag", "你好", "aloha" }; | 
|  | 0 | 104 |  |             var otherStrings = new[] { "good bye", "au revoir", "auf wiedersehen", "再見", "aloha" }; | 
|  |  | 105 |  |  | 
|  | 0 | 106 |  |             var intComparer = new ModelComparer<int[]>(); | 
|  | 0 | 107 |  |             var stringComparer = new ModelComparer<string[]>(); | 
|  |  | 108 |  |  | 
|  |  | 109 |  |             // We have to use Assert.True/False instead of .Equal/.NotEqual or we won't actually exercise the ModelCompa | 
|  |  | 110 |  |             // Also, we use deep copies for some comparisons to make sure the ModelComparer isn't falling back on refere | 
|  | 0 | 111 |  |             Assert.True(intComparer.Equals(ints, ints)); | 
|  | 0 | 112 |  |             Assert.True(stringComparer.Equals(strings, strings)); | 
|  |  | 113 |  |  | 
|  | 0 | 114 |  |             Assert.True(intComparer.Equals(ints, (int[])ints.Clone())); | 
|  | 0 | 115 |  |             Assert.True(stringComparer.Equals((string[])strings.Clone(), strings)); | 
|  |  | 116 |  |  | 
|  | 0 | 117 |  |             Assert.False(intComparer.Equals(ints, otherInts)); | 
|  | 0 | 118 |  |             Assert.False(stringComparer.Equals(otherStrings, strings)); | 
|  |  | 119 |  |  | 
|  | 0 | 120 |  |             Assert.False(intComparer.Equals(ints, new[] { 7 })); | 
|  | 0 | 121 |  |             Assert.False(stringComparer.Equals(new[] { "who?" }, strings)); | 
|  |  | 122 |  |  | 
|  |  | 123 |  |             // Test collections of varying lengths. | 
|  | 0 | 124 |  |             Assert.False(intComparer.Equals(ints, longerInts)); | 
|  | 0 | 125 |  |             Assert.False(intComparer.Equals(longerInts, ints)); | 
|  | 0 | 126 |  |         } | 
|  |  | 127 |  |  | 
|  |  | 128 |  |         [Fact] | 
|  |  | 129 |  |         public void CanCompareDictionaries() | 
|  |  | 130 |  |         { | 
|  | 0 | 131 |  |             var x = new Dictionary<string, double>() { { "pi", 3.14 }, { "e", 2.718 } }; | 
|  | 0 | 132 |  |             var notX = new Dictionary<string, double>() { { "m", 13.3 }, { "v", 77 } }; | 
|  |  | 133 |  |  | 
|  | 0 | 134 |  |             var y = new Dictionary<string, object>() { { "a", "test" }, { "b", "test2" } }; | 
|  | 0 | 135 |  |             var notY = new Dictionary<string, object>() { { "w", "other" }, { "u", "side" } }; | 
|  |  | 136 |  |  | 
|  | 0 | 137 |  |             var doubleComparer = new ModelComparer<Dictionary<string, double>>(); | 
|  | 0 | 138 |  |             var stringComparer = new ModelComparer<Dictionary<string, object>>(); | 
|  |  | 139 |  |  | 
|  |  | 140 |  |             // We have to use Assert.True/False instead of .Equal/.NotEqual or we won't actually exercise the ModelCompa | 
|  |  | 141 |  |             // Also, we use deep copies for some comparisons to make sure the ModelComparer isn't falling back on refere | 
|  | 0 | 142 |  |             Assert.True(doubleComparer.Equals(x, x)); | 
|  | 0 | 143 |  |             Assert.True(stringComparer.Equals(y, y)); | 
|  |  | 144 |  |  | 
|  | 0 | 145 |  |             Assert.True(doubleComparer.Equals(x, new Dictionary<string, double>(x))); | 
|  | 0 | 146 |  |             Assert.True(stringComparer.Equals(new Dictionary<string, object>(y), y)); | 
|  |  | 147 |  |  | 
|  | 0 | 148 |  |             Assert.False(doubleComparer.Equals(x, notX)); | 
|  | 0 | 149 |  |             Assert.False(stringComparer.Equals(notY, y)); | 
|  |  | 150 |  |  | 
|  | 0 | 151 |  |             Assert.False(doubleComparer.Equals(x, new Dictionary<string, double>() { { "r", 12.0 } })); | 
|  | 0 | 152 |  |             Assert.False(stringComparer.Equals(new Dictionary<string, object>() { { "c", "other" } }, y)); | 
|  | 0 | 153 |  |         } | 
|  |  | 154 |  |  | 
|  |  | 155 |  |         [Fact] | 
|  |  | 156 |  |         public void IEquatableTakesPrecedenceOverIEnumerable() | 
|  |  | 157 |  |         { | 
|  | 0 | 158 |  |             var ints = new BiasedList(1, 2, 3); | 
|  | 0 | 159 |  |             var odds = new BiasedList(1, 3, 5); | 
|  | 0 | 160 |  |             var evens = new BiasedList(2, 4, 6); | 
|  |  | 161 |  |  | 
|  | 0 | 162 |  |             var comparer = new ModelComparer<BiasedList>(); | 
|  |  | 163 |  |  | 
|  | 0 | 164 |  |             Assert.True(comparer.Equals(ints, ints)); | 
|  | 0 | 165 |  |             Assert.True(comparer.Equals(ints, new BiasedList(ints))); | 
|  |  | 166 |  |  | 
|  |  | 167 |  |             // ints and odds should compare as equal because BiasedList only looks at the first element. | 
|  | 0 | 168 |  |             Assert.True(comparer.Equals(odds, ints)); | 
|  | 0 | 169 |  |             Assert.True(comparer.Equals(new BiasedList(odds), ints)); | 
|  |  | 170 |  |  | 
|  | 0 | 171 |  |             Assert.False(comparer.Equals(ints, evens)); | 
|  | 0 | 172 |  |             Assert.False(comparer.Equals(ints, new BiasedList(evens))); | 
|  | 0 | 173 |  |         } | 
|  |  | 174 |  |  | 
|  |  | 175 |  |         [Fact] | 
|  |  | 176 |  |         public void CanComparePolymorphicObjects() | 
|  |  | 177 |  |         { | 
|  | 0 | 178 |  |             var cat = new Cat("Gizmo"); | 
|  | 0 | 179 |  |             var otherCat = new Cat("Ness"); | 
|  | 0 | 180 |  |             var dog = new Dog("Mara"); | 
|  | 0 | 181 |  |             var otherDog = new Dog("Daisy"); | 
|  |  | 182 |  |  | 
|  | 0 | 183 |  |             var comparer = new ModelComparer<Animal>(); | 
|  |  | 184 |  |  | 
|  | 0 | 185 |  |             Assert.Equal(dog, dog, comparer); | 
|  | 0 | 186 |  |             Assert.Equal(dog, new Dog(dog), comparer); | 
|  | 0 | 187 |  |             Assert.Equal(cat, cat, comparer); | 
|  | 0 | 188 |  |             Assert.Equal(cat, new Cat(cat), comparer); | 
|  |  | 189 |  |  | 
|  | 0 | 190 |  |             Assert.NotEqual(dog, otherDog, comparer); | 
|  | 0 | 191 |  |             Assert.NotEqual(cat, otherCat, comparer); | 
|  | 0 | 192 |  |             Assert.NotEqual(dog, cat, comparer); | 
|  | 0 | 193 |  |         } | 
|  |  | 194 |  |  | 
|  |  | 195 |  |         [Fact] | 
|  |  | 196 |  |         public void CanCompareDtos() | 
|  |  | 197 |  |         { | 
|  | 0 | 198 |  |             var model = new Model() { Name = "Magical Trevor", Age = 11 }; | 
|  | 0 | 199 |  |             var sameModel = new Model(model); | 
|  | 0 | 200 |  |             var differentModel = new Model() { Name = "Mr. Stabby", Age = 35 }; | 
|  |  | 201 |  |  | 
|  | 0 | 202 |  |             var comparer = new ModelComparer<Model>(); | 
|  |  | 203 |  |  | 
|  | 0 | 204 |  |             Assert.Equal(model, model, comparer); | 
|  | 0 | 205 |  |             Assert.Equal(model, sameModel, comparer); | 
|  | 0 | 206 |  |             Assert.NotEqual(model, differentModel, comparer); | 
|  | 0 | 207 |  |         } | 
|  |  | 208 |  |  | 
|  |  | 209 |  |         [Fact] | 
|  |  | 210 |  |         public void ComparingMarkerClassesAlwaysReturnsTrue() | 
|  |  | 211 |  |         { | 
|  | 0 | 212 |  |             var marker = new Empty(); | 
|  | 0 | 213 |  |             var otherMarker = new Empty(); | 
|  |  | 214 |  |  | 
|  | 0 | 215 |  |             Assert.Equal(marker, marker, new ModelComparer<Empty>()); | 
|  | 0 | 216 |  |             Assert.Equal(marker, otherMarker, new ModelComparer<Empty>()); | 
|  | 0 | 217 |  |         } | 
|  |  | 218 |  |  | 
|  |  | 219 |  |         [Fact] | 
|  |  | 220 |  |         public void CanCompareDifferentEnumerableTypes() | 
|  |  | 221 |  |         { | 
|  | 0 | 222 |  |             var intArray = new[] { 1, 2, 3 }; | 
|  | 0 | 223 |  |             var intList = new List<int>() { 1, 2, 3 }; | 
|  |  | 224 |  |  | 
|  | 0 | 225 |  |             var intComparer = new ModelComparer<IEnumerable<int>>(); | 
|  |  | 226 |  |  | 
|  |  | 227 |  |             // We have to use Assert.True/False instead of .Equal/.NotEqual or we won't actually exercise the ModelCompa | 
|  | 0 | 228 |  |             Assert.True(intComparer.Equals(intArray, intList)); | 
|  | 0 | 229 |  |         } | 
|  |  | 230 |  |  | 
|  |  | 231 |  |         [Fact] | 
|  |  | 232 |  |         public void IntegerComparisonsUseWidestType() | 
|  |  | 233 |  |         { | 
|  | 0 | 234 |  |             byte b = 10; | 
|  | 0 | 235 |  |             short s = 10; | 
|  | 0 | 236 |  |             int i = 10; | 
|  | 0 | 237 |  |             long l = 10; | 
|  |  | 238 |  |  | 
|  | 0 | 239 |  |             var comparer = new ModelComparer<object>(); | 
|  |  | 240 |  |  | 
|  | 0 | 241 |  |             Assert.Equal(b, s, comparer); | 
|  | 0 | 242 |  |             Assert.Equal(b, i, comparer); | 
|  | 0 | 243 |  |             Assert.Equal(b, l, comparer); | 
|  | 0 | 244 |  |             Assert.Equal(s, b, comparer); | 
|  | 0 | 245 |  |             Assert.Equal(s, i, comparer); | 
|  | 0 | 246 |  |             Assert.Equal(s, l, comparer); | 
|  | 0 | 247 |  |             Assert.Equal(i, b, comparer); | 
|  | 0 | 248 |  |             Assert.Equal(i, s, comparer); | 
|  | 0 | 249 |  |             Assert.Equal(i, l, comparer); | 
|  | 0 | 250 |  |             Assert.Equal(l, b, comparer); | 
|  | 0 | 251 |  |             Assert.Equal(l, s, comparer); | 
|  | 0 | 252 |  |             Assert.Equal(l, i, comparer); | 
|  | 0 | 253 |  |         } | 
|  |  | 254 |  |  | 
|  |  | 255 |  |         [Fact] | 
|  |  | 256 |  |         public void CanCompareComplexModels() | 
|  |  | 257 |  |         { | 
|  | 0 | 258 |  |             var homeAddress = new USAddress() | 
|  | 0 | 259 |  |             { | 
|  | 0 | 260 |  |                 Street = "308 Negra Arroyo Lane", | 
|  | 0 | 261 |  |                 City = "Albuquerque", | 
|  | 0 | 262 |  |                 State = "New Mexico", | 
|  | 0 | 263 |  |                 ZipCode = 87104 | 
|  | 0 | 264 |  |             }; | 
|  |  | 265 |  |  | 
|  | 0 | 266 |  |             var alternateAddress = new USAddress() | 
|  | 0 | 267 |  |             { | 
|  | 0 | 268 |  |                 Street = "Los Pollos Hermanos, 123 Central Ave Southeast", | 
|  | 0 | 269 |  |                 City = "Albuquerque", | 
|  | 0 | 270 |  |                 State = "New Mexico", | 
|  | 0 | 271 |  |                 ZipCode = 87108 | 
|  | 0 | 272 |  |             }; | 
|  |  | 273 |  |  | 
|  | 0 | 274 |  |             var theOneWhoKnocks = new Customer() | 
|  | 0 | 275 |  |             { | 
|  | 0 | 276 |  |                 FirstName = "Walter", | 
|  | 0 | 277 |  |                 LastName = "White", | 
|  | 0 | 278 |  |                 Aliases = new[] { "Heisenberg", "The One Who Knocks" }, | 
|  | 0 | 279 |  |                 Age = 51, | 
|  | 0 | 280 |  |                 Address = homeAddress, | 
|  | 0 | 281 |  |                 Directions = new List<Direction?>() { null, Direction.Left, null, Direction.Right }, | 
|  | 0 | 282 |  |                 Occupations = new Dictionary<string, Address>() | 
|  | 0 | 283 |  |                 { | 
|  | 0 | 284 |  |                     { "Husband and father", homeAddress }, | 
|  | 0 | 285 |  |                     { "Career criminal", alternateAddress } | 
|  | 0 | 286 |  |                 } | 
|  | 0 | 287 |  |             }; | 
|  |  | 288 |  |  | 
|  | 0 | 289 |  |             var walterWhite = theOneWhoKnocks.Clone(); | 
|  |  | 290 |  |  | 
|  | 0 | 291 |  |             var gustavoFring = new Customer() | 
|  | 0 | 292 |  |             { | 
|  | 0 | 293 |  |                 FirstName = "Gustavo", | 
|  | 0 | 294 |  |                 LastName = "Fring", | 
|  | 0 | 295 |  |                 Age = 49, | 
|  | 0 | 296 |  |                 Address = alternateAddress | 
|  | 0 | 297 |  |             }; | 
|  |  | 298 |  |  | 
|  | 0 | 299 |  |             var comparer = new ModelComparer<Customer>(); | 
|  |  | 300 |  |  | 
|  | 0 | 301 |  |             Assert.Equal(theOneWhoKnocks, theOneWhoKnocks, comparer); | 
|  | 0 | 302 |  |             Assert.Equal(walterWhite, theOneWhoKnocks, comparer); | 
|  | 0 | 303 |  |             Assert.NotEqual(gustavoFring, theOneWhoKnocks, comparer); | 
|  | 0 | 304 |  |         } | 
|  |  | 305 |  |  | 
|  |  | 306 |  |         private class Empty { } | 
|  |  | 307 |  |  | 
|  |  | 308 |  |         private class Model | 
|  |  | 309 |  |         { | 
|  | 0 | 310 |  |             public Model() { } | 
|  |  | 311 |  |  | 
|  | 0 | 312 |  |             public Model(Model other) | 
|  |  | 313 |  |             { | 
|  | 0 | 314 |  |                 Name = other.Name; | 
|  | 0 | 315 |  |                 Age = other.Age; | 
|  | 0 | 316 |  |             } | 
|  |  | 317 |  |  | 
|  | 0 | 318 |  |             public string Name { get; set; } | 
|  |  | 319 |  |  | 
|  | 0 | 320 |  |             public int Age { get; set; } | 
|  |  | 321 |  |         } | 
|  |  | 322 |  |  | 
|  |  | 323 |  |         private struct FancyDirection : IEquatable<FancyDirection> | 
|  |  | 324 |  |         { | 
|  |  | 325 |  |             private readonly string _value; | 
|  |  | 326 |  |  | 
|  | 0 | 327 |  |             public static readonly FancyDirection Left = new FancyDirection("left"); | 
|  |  | 328 |  |  | 
|  | 0 | 329 |  |             public static readonly FancyDirection Right = new FancyDirection("right"); | 
|  |  | 330 |  |  | 
|  |  | 331 |  |             private FancyDirection(string name) | 
|  |  | 332 |  |             { | 
|  | 0 | 333 |  |                 _value = name ?? throw new ArgumentNullException(nameof(name)); | 
|  | 0 | 334 |  |             } | 
|  |  | 335 |  |  | 
|  | 0 | 336 |  |             public static bool operator ==(FancyDirection lhs, FancyDirection rhs) => Equals(lhs, rhs); | 
|  |  | 337 |  |  | 
|  | 0 | 338 |  |             public static bool operator !=(FancyDirection lhs, FancyDirection rhs) => !Equals(lhs, rhs); | 
|  |  | 339 |  |  | 
|  | 0 | 340 |  |             public bool Equals(FancyDirection other) => _value == other._value; | 
|  |  | 341 |  |  | 
|  | 0 | 342 |  |             public override bool Equals(object obj) => obj is FancyDirection ? Equals((FancyDirection)obj) : false; | 
|  |  | 343 |  |  | 
|  | 0 | 344 |  |             public override int GetHashCode() => _value.GetHashCode(); | 
|  |  | 345 |  |  | 
|  | 0 | 346 |  |             public override string ToString() => _value; | 
|  |  | 347 |  |         } | 
|  |  | 348 |  |  | 
|  |  | 349 |  |         // Biased lists only care about the first element when it comes to equality comparison. | 
|  |  | 350 |  |         private class BiasedList : IEnumerable<int>, IEquatable<BiasedList> | 
|  |  | 351 |  |         { | 
|  |  | 352 |  |             private readonly int[] _values; | 
|  |  | 353 |  |  | 
|  | 0 | 354 |  |             public BiasedList(params int[] values) | 
|  |  | 355 |  |             { | 
|  | 0 | 356 |  |                 if (values == null) | 
|  |  | 357 |  |                 { | 
|  | 0 | 358 |  |                     throw new ArgumentNullException(nameof(values)); | 
|  |  | 359 |  |                 } | 
|  |  | 360 |  |  | 
|  | 0 | 361 |  |                 if (values.Length == 0) | 
|  |  | 362 |  |                 { | 
|  | 0 | 363 |  |                     throw new ArgumentOutOfRangeException(nameof(values), "List must be non-empty"); | 
|  |  | 364 |  |                 } | 
|  |  | 365 |  |  | 
|  | 0 | 366 |  |                 _values = values; | 
|  | 0 | 367 |  |             } | 
|  |  | 368 |  |  | 
|  | 0 | 369 |  |             public BiasedList(BiasedList other) | 
|  |  | 370 |  |             { | 
|  | 0 | 371 |  |                 if (other == null) | 
|  |  | 372 |  |                 { | 
|  | 0 | 373 |  |                     throw new ArgumentNullException(nameof(other)); | 
|  |  | 374 |  |                 } | 
|  |  | 375 |  |  | 
|  | 0 | 376 |  |                 _values = (int[])other._values.Clone(); | 
|  | 0 | 377 |  |             } | 
|  |  | 378 |  |  | 
|  | 0 | 379 |  |             public bool Equals(BiasedList other) => other != null && this.First() == other.First(); | 
|  |  | 380 |  |  | 
|  | 0 | 381 |  |             public IEnumerator<int> GetEnumerator() => ((IEnumerable<int>)_values).GetEnumerator(); | 
|  |  | 382 |  |  | 
|  | 0 | 383 |  |             IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); | 
|  |  | 384 |  |         } | 
|  |  | 385 |  |  | 
|  |  | 386 |  |         private abstract class Animal | 
|  |  | 387 |  |         { | 
|  | 0 | 388 |  |             protected Animal(string name) | 
|  |  | 389 |  |             { | 
|  | 0 | 390 |  |                 Name = name; | 
|  | 0 | 391 |  |             } | 
|  |  | 392 |  |  | 
|  |  | 393 |  |             public abstract string Noise { get; } | 
|  |  | 394 |  |  | 
|  | 0 | 395 |  |             public string Name { get; } | 
|  |  | 396 |  |         } | 
|  |  | 397 |  |  | 
|  |  | 398 |  |         private sealed class Dog : Animal, IEquatable<Dog> | 
|  |  | 399 |  |         { | 
|  |  | 400 |  |             private readonly Random _random; | 
|  |  | 401 |  |  | 
|  | 0 | 402 |  |             public Dog(string name) : base(name) | 
|  |  | 403 |  |             { | 
|  | 0 | 404 |  |                 _random = new Random(); | 
|  | 0 | 405 |  |             } | 
|  |  | 406 |  |  | 
|  | 0 | 407 |  |             public Dog(Dog dog) : this(dog.Name) { } | 
|  |  | 408 |  |  | 
|  |  | 409 |  |             // Add some randomness to ensure that IEquatable always takes precedence. | 
|  | 0 | 410 |  |             public override string Noise => _random.Next() % 2 == 0 ? "woof!" : "bark!"; | 
|  |  | 411 |  |  | 
|  | 0 | 412 |  |             public bool Equals(Dog other) => other != null && this.Name == other.Name; | 
|  |  | 413 |  |         } | 
|  |  | 414 |  |  | 
|  |  | 415 |  |         private sealed class Cat : Animal, IEquatable<Cat> | 
|  |  | 416 |  |         { | 
|  | 0 | 417 |  |             public Cat(string name) : base(name) { } | 
|  |  | 418 |  |  | 
|  | 0 | 419 |  |             public Cat(Cat cat) : this(cat.Name) { } | 
|  |  | 420 |  |  | 
|  | 0 | 421 |  |             public override string Noise => "meow!"; | 
|  |  | 422 |  |  | 
|  | 0 | 423 |  |             public bool Equals(Cat other) => other != null && this.Name == other.Name; | 
|  |  | 424 |  |         } | 
|  |  | 425 |  |  | 
|  |  | 426 |  |         private class Customer | 
|  |  | 427 |  |         { | 
|  | 0 | 428 |  |             public Customer() { } | 
|  |  | 429 |  |  | 
|  | 0 | 430 |  |             public Customer(Customer other) | 
|  |  | 431 |  |             { | 
|  | 0 | 432 |  |                 Aliases = other.Aliases.ToArray(); | 
|  | 0 | 433 |  |                 Age = other.Age; | 
|  | 0 | 434 |  |                 Address = other.Address.Clone(); | 
|  | 0 | 435 |  |                 FirstName = other.FirstName; | 
|  | 0 | 436 |  |                 LastName = other.LastName; | 
|  | 0 | 437 |  |                 Directions = other.Directions.ToList(); | 
|  | 0 | 438 |  |                 Occupations = other.Occupations.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Clone()); | 
|  | 0 | 439 |  |             } | 
|  |  | 440 |  |  | 
|  | 0 | 441 |  |             public string Name => FirstName + " " + LastName; | 
|  |  | 442 |  |  | 
|  | 0 | 443 |  |             public IList<string> Aliases { get; set; } | 
|  |  | 444 |  |  | 
|  | 0 | 445 |  |             public int? Age { get; set; } | 
|  |  | 446 |  |  | 
|  | 0 | 447 |  |             public Address Address { get; set; } | 
|  |  | 448 |  |  | 
|  | 0 | 449 |  |             public string FirstName { get; set; } | 
|  |  | 450 |  |  | 
|  | 0 | 451 |  |             public string LastName { get; set; } | 
|  |  | 452 |  |  | 
|  | 0 | 453 |  |             public List<Direction?> Directions { get; set; } | 
|  |  | 454 |  |  | 
|  | 0 | 455 |  |             public Dictionary<string, Address> Occupations { get; set; } | 
|  |  | 456 |  |  | 
|  | 0 | 457 |  |             public Customer Clone() => new Customer(this); | 
|  |  | 458 |  |         } | 
|  |  | 459 |  |  | 
|  |  | 460 |  |         private abstract class Address | 
|  |  | 461 |  |         { | 
|  | 0 | 462 |  |             public string Street { get; set; } | 
|  |  | 463 |  |  | 
|  | 0 | 464 |  |             public string City { get; set; } | 
|  |  | 465 |  |  | 
|  |  | 466 |  |             public abstract Address Clone(); | 
|  |  | 467 |  |         } | 
|  |  | 468 |  |  | 
|  |  | 469 |  |         private sealed class USAddress : Address | 
|  |  | 470 |  |         { | 
|  | 0 | 471 |  |             public USAddress() { } | 
|  |  | 472 |  |  | 
|  | 0 | 473 |  |             public USAddress(USAddress other) | 
|  |  | 474 |  |             { | 
|  | 0 | 475 |  |                 Street = other.Street; | 
|  | 0 | 476 |  |                 City = other.City; | 
|  | 0 | 477 |  |                 State = other.State; | 
|  | 0 | 478 |  |                 ZipCode = other.ZipCode; | 
|  | 0 | 479 |  |             } | 
|  |  | 480 |  |  | 
|  | 0 | 481 |  |             public string State { get; set; } | 
|  |  | 482 |  |  | 
|  | 0 | 483 |  |             public int ZipCode { get; set; } | 
|  |  | 484 |  |  | 
|  | 0 | 485 |  |             public override Address Clone() => new USAddress(this); | 
|  |  | 486 |  |         } | 
|  |  | 487 |  |     } | 
|  |  | 488 |  | } |