Search code examples
c#tuplesequalshashsetgethashcode

Avoiding duplicates in a HashSet of custom types in C#


I have the following custom class deriving from Tuple:

public class CustomTuple : Tuple<List<string>, DateTime?>
{
  public CustomTuple(IEnumerable<string> strings, DateTime? time)
      : base(strings.OrderBy(x => x).ToList(), time)
  {
  }
}

and a HashSet<CustomTuple>. The problem is that when I add items to the set, they are not recognised as duplicates. i.e. this outputs 2, but it should output 1:

void Main()
{
    HashSet<CustomTuple> set = new HashSet<CustomTuple>();

    var a = new CustomTuple(new List<string>(), new DateTime?());
    var b = new CustomTuple(new List<string>(), new DateTime?());

    set.Add(a);
    set.Add(b);

    Console.Write(set.Count); // Outputs 2
}

How can I override the Equals and GetHashCode methods to cause this code to output a set count of 1?


Solution

  • This is is what I went for, which outputs 1 as desired:

    private class CustomTuple : Tuple<List<string>, DateTime?>
    {
      public CustomTuple(IEnumerable<string> strings, DateTime? time)
            : base(strings.OrderBy(x => x).ToList(), time)
        {
        }
    
      public override bool Equals(object obj)
      {
          if (obj == null || GetType() != obj.GetType())
          {
              return false;
          }
    
          var that = (CustomTuple) obj;
    
          if (Item1 == null && that.Item1 != null || Item1 != null && that.Item1 == null) return false;
          if (Item2 == null && that.Item2 != null || Item2 != null && that.Item2 == null) return false;
    
          if (!Item2.Equals(that.Item2)) return false;
          if (that.Item1.Count != Item1.Count) return false;
          for (int i = 0; i < Item1.Count; i++)
          {
              if (!Item1[i].Equals(that.Item1[i])) return false;
          }
    
          return true;
      }
    
      public override int GetHashCode()
      {
          int hash = 17;
          hash = hash*23 + Item2.GetHashCode();
          return Item1.Aggregate(hash, (current, s) => current*23 + s.GetHashCode());
      }
    }