Categories
.net c# iola Programming

Iimplement brain damage

Had this annoying bug today where something that seemed perfectly resonable just didn’t work. After much investigation it appears that once again brain damage from Java has managed to over into C#. The problem is illustrated with the following code (You can ignore the Tuple for now):

public struct Tuple < TFirst,TSecond >
{
    public TFirst first;
    public TSecond second;

    public Tuple(TFirst first, TSecond second)
   {
	this.first = first;
	this.second = second;
   }
}

[...]

Tuple < int,string > t = new Tuple < int,string >(1, "1");
Tuple < int,string > t2 = new Tuple < int,string >(2, "2");

List < tuple < int,string > > lt = new List < tuple < int,string > >();
lt.Add(t);
lt.Add(t2);

List < tuple < int,string > > lt2 = new List < tuple < int,string > >();
lt2.Add(t);
lt2.Add(t2);

System.Console.WriteLine("eq {0}, == {1}", lt.Equals(lt2), lt == lt2);

Which gives the following result:

eq False, == False

Ok that was strange, in Python and C++ one doesn’t have to use Equal or anything similar and furthermore == gives the correct result since it compares elements memberwise instead of just checking the reference. I recalled that in Java one has to use Equal on strings, since == just compares references. So I googled around and found an explaination in point in the following link at 6.7. Note all the special cases. The best part is the following paragraph: “The implementation of Equals() in System.Object (the one you’ll inherit by default if you write a class) compares identity, i.e. it’s the same as operator==”. Apparently List doesn’t to that, despite their efforts to help, great… So we’ll have to do that ourselves:

public static IEnumerable < tuple < T1,T2 > > zip < T1,T2 >
(IEnumerable < T1 > l1, IEnumerable < T2 > l2)
{
   IEnumerator < T1 > i1 = l1.GetEnumerator();
   IEnumerator < T2 > i2 = l2.GetEnumerator();

   while (i1.MoveNext() && i2.MoveNext())
       yield return new Tuple < T1,T2 > (i1.Current, i2.Current);
}

public static bool sorted_lists_equal < T > (List < T > l1, List < T > l2)
where T:IEquatable < T >
{
   if (l1.Count != l2.Count)
       return false;
   foreach (Tuple < T, T > t in zip < T,T >(l1, l2)) {
        if (!t.first.Equals(t.second))
              return false;
         }
   return true;
}

IEquatable is an interface what basically says that it will compare by value. But even though our Tuple implementation is a struct and thus is a ValueType it doesn’t implement this interface (the Equals method). It instead automatically defines the == operator to work as one expects since it’s a ValueType. So we have to change Tuple:

public struct Tuple < TFirst,TSecond >
: IEquatable < Tuple < TFirst,TSecond > >
{
   public TFirst first;
   public TSecond second;

   public Tuple(TFirst first, TSecond second)
   {
	this.first = first;
	this.second = second;
   }

   public bool Equals(Tuple < TFirst,TSecond > other)
   {
      return first.Equals(other.first) && second.Equals(other.second);
   }

    public static bool operator==(Tuple < TFirst,TSecond > lhs,
    Tuple < TFirst,TSecond > rhs)
    {
	return lhs.Equals(rhs);
    }

    public static bool operator!=(Tuple < TFirst,TSecond > lhs,
    Tuple < TFirst,TSecond > rhs)
    {
	return !(lhs == rhs);
    }
}

The last two functions was added because now that we’re implementing the IEquatable interface the compiler doesn’t seem to want to implement == and !=.

So instead of the following Python code:

a = [(1,"1"), (2,"2")]
b = [(1,"1"), (2,"2")]
a == b

We have to do the big mess above :-/