I have been looking at LINQ extension methods and have blogged about what I learned from them in my blog space. Next in line is the SequenceEqual() method. Here’s the description about this method: “Determines whether two sequences are equal by comparing the elements by using the default equality comparer for their type.” Let’s play with some code: 1: int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
2: // int[] numbersCopy = numbers;
3: int[] numbersCopy = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
4:
5: Console.WriteLine(numbers.SequenceEqual(numbersCopy));
This gives an output of ‘True’ – basically compares each of the elements in the two arrays and returns true in this case. The result is same even if you uncomment line 2 and comment line 3 (I didn’t need to say that now did I?). So then what happens for custom types? For this, I created a Product class with the following definition:
1: class Product
2: {
3: public int ProductId { get; set; }
4: public string Name { get; set; }
5: public string Category { get; set; }
6: public DateTime MfgDate { get; set; }
7: public Status Status { get; set; }
8: }
9:
10: public enum Status
11: {
12: Active = 1,
13: InActive = 2,
14: OffShelf = 3,
15: }
In my calling code, I’m just adding a few product items:
1: private static List<Product> GetProducts()
2: {
3: return new List<Product>
4: {
5: new Product
6: {
7: ProductId = 1,
8: Name = "Laptop",
9: Category = "Computer",
10: MfgDate = new DateTime(2003, 4, 3),
11: Status = Status.Active,
12: },
13: new Product
14: {
15: ProductId = 2,
16: Name = "Compact Disc",
17: Category = "Water Sport",
18: MfgDate = new DateTime(2009, 12, 3),
19: Status = Status.InActive,
20: },
21: new Product
22: {
23: ProductId = 3,
24: Name = "Floppy",
25: Category = "Computer",
26: MfgDate = new DateTime(1993, 3, 7),
27: Status = Status.OffShelf,
28: },
29: };
30: }
Now for the actual check:
1: List<Product> products1 = GetProducts();
2: List<Product> products2 = GetProducts();
3:
4: Console.WriteLine(products1.SequenceEqual(products2));
This one returns ‘False’ and the reason is simple – this one checks for reference equality and the products in the both the lists get different ‘memory addresses’ (sounds like I’m talking in ‘C’).
In order to modify this behavior and return a ‘True’ result, we need to modify the Product class as follows:
1: class Product : IEquatable<Product>
2: {
3: public int ProductId { get; set; }
4: public string Name { get; set; }
5: public string Category { get; set; }
6: public DateTime MfgDate { get; set; }
7: public Status Status { get; set; }
8:
9: public override bool Equals(object obj)
10: {
11: return Equals(obj as Product);
12: }
13:
14: public bool Equals(Product other)
15: {
16: //Check whether the compared object is null.
17: if (ReferenceEquals(other, null)) return false;
18:
19: //Check whether the compared object references the same data.
20: if (ReferenceEquals(this, other)) return true;
21:
22: //Check whether the products' properties are equal.
23: return ProductId.Equals(other.ProductId)
24: && Name.Equals(other.Name)
25: && Category.Equals(other.Category)
26: && MfgDate.Equals(other.MfgDate)
27: && Status.Equals(other.Status);
28: }
29:
30: // If Equals() returns true for a pair of objects
31: // then GetHashCode() must return the same value for these objects.
32: // read why in the following articles:
33: // http://geekswithblogs.net/akraus1/archive/2010/02/28/138234.aspx
34: // http://stackoverflow.com/questions/371328/why-is-it-important-to-override-gethashcode-when-equals-method-is-overriden-in-c
35: public override int GetHashCode()
36: {
37: //Get hash code for the ProductId field.
38: int hashProductId = ProductId.GetHashCode();
39:
40: //Get hash code for the Name field if it is not null.
41: int hashName = Name == null ? 0 : Name.GetHashCode();
42:
43: //Get hash code for the ProductId field.
44: int hashCategory = Category.GetHashCode();
45:
46: //Get hash code for the ProductId field.
47: int hashMfgDate = MfgDate.GetHashCode();
48:
49: //Get hash code for the ProductId field.
50: int hashStatus = Status.GetHashCode();
51: //Calculate the hash code for the product.
52: return hashProductId ^ hashName ^ hashCategory & hashMfgDate & hashStatus;
53: }
54:
55: public static bool operator ==(Product a, Product b)
56: {
57: // Enable a == b for null references to return the right value
58: if (ReferenceEquals(a, b))
59: {
60: return true;
61: }
62: // If one is null and the other not. Remember a==null will lead to Stackoverflow!
63: if (ReferenceEquals(a, null))
64: {
65: return false;
66: }
67: return a.Equals((object)b);
68: }
69:
70: public static bool operator !=(Product a, Product b)
71: {
72: return !(a == b);
73: }
74: }
Now THAT kinda looks overwhelming. But lets take one simple step at a time.
Ok first thing you’ve noticed is that the class implements IEquatable<Product> interface – the key step towards achieving our goal. This interface provides us with an ‘Equals’ method to perform the test for equality with another Product object, in this case.
This method is called in the following situations:
when you do a ProductInstance.Equals(AnotherProductInstance) and
when you perform actions like Contains<T>, IndexOf() or Remove() on your collection
Coming to the Equals method defined line 14 onwards. The two ‘if’ blocks check for null and referential equality using the ReferenceEquals() method defined in the Object class. Line 23 is where I’m doing the actual check on the properties of the Product instances. This is what returns the ‘True’ for us when we run the application.
I have also overridden the Object.Equals() method which calls the Equals() method of the interface. One thing to remember is that anytime you override the Equals() method, its’ a good practice to override the GetHashCode() method and overload the ‘==’ and the ‘!=’ operators. For detailed information on this, please read this and this.
Since we’ve overloaded the operators as well, we get ‘True’ when we do actions like:
1: Console.WriteLine(products1.Contains(products2[0]));
2: Console.WriteLine(products1[0] == products2[0]);
This completes the full circle on the SequenceEqual() method. See the code used in the article here.