| | | 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 | | } |