views:

81

answers:

2

I have some code that looks like the following. First I have some domain classes and some special comparators for them.

public class Fruit {
  public int Calories { get; set; }
  public string Name { get; set; }
}

public class FruitEqualityComparer : IEqualityComparer<Fruit> {
  // ...
}

// A basket is just a group of Fruits.
public class BasketEqualityComparer : IEqualityComparer<IEnumerable<Fruit>> {
  // ...
}

Next, I have a helper class called ConstraintChecker. It has a simple BaseEquals method that makes sure some simple base cases are considered:

public static class ConstraintChecker {
  public static bool BaseEquals(T lhs, T rhs) {
    bool sameObject = l == r;
    bool leftNull = l == null;
    bool rightNull = r == null;

    return sameObject && !leftNull && !rightNull;
  }

There's also a SemanticEquals method which is just a BaseEquals check and a comparator function that you specify.

  public static bool SemanticEquals<T>(
    T lhs, T rhs, Func<T, T, bool> f) {
    return BaseEquals(lhs, rhs) && f(lhs, rhs);
  }

And finally there's a SemanticSequenceEquals method which accepts two IEnumerable<T> instances to compare, and an IEqualityComparer instance that will get called on each pair of elements in the list via Enumerable.SequenceEquals.

  public static bool SemanticSequenceEquals<T, U, V>(U lhs,
                                                     U rhs,
                                                     V comparator)
    where U : IEnumerable<T>
    where V : IEqualityComparer<T> {
    return SemanticEquals(lhs, rhs, (l, r) => lhs.SequenceEqual(rhs, comparator));
  }
} // end of ConstraintChecker

The point of SemanticSequenceEquals is that you don't have to define two comparators whenever you want to compare both IEnumerable<T> and T instances; now you can just specify an IEqualityComparer<T> and it will also handle lists when you invoke SemanticSequenceEquals. So I could get rid of the BasketEqualityComparer class, which would be nice.

But there's a problem. The C# compiler can't figure out the types involved when you invoke SemanticSequenceEquals:

// Error! Compiler can't infer the type parameters.
return ConstraintChecker.SemanticSequenceEquals(lhs, rhs,
  new FruitEqualityComparer());

If I specify them explicitly, it works:

return ConstraintChecker.SemanticSequenceEquals<Fruit, IEnumerable<Fruit>,
  IEqualityComparer<Fruit>> (lhs, rhs, new FruitEqualityComparer());

Obviously, that's a huge hassle, and it's not very DRY. What can I change here so that I don't have to write the type parameters explicitly?

+2  A: 

Try just specifying T without the U and V like this.

public static bool SemanticSequenceEquals<T>(
    IEnumerable<T> lhs, 
    IEnumerable<T> rhs, 
    IEqualityComparer<T> comparator)
{
    return SemanticEquals(lhs, rhs, (l, r) => lhs.SequenceEqual(rhs, comparator));
}
juharr
Why didn't I think of that? This is the most obvious solution, though I still have to specify the type parameter for `T` explicitly. It looks like the compiler can't infer T when it's a shared type parameter of two method parameters.
John Feminella
+1  A: 

You could explicitly type your arguments to SemanticSequenceEquals. This seems to compile fine for me:

public static bool SemanticSequenceEquals<T>(IEnumerable<T> lhs, IEnumerable<T> rhs, IEqualityComparer<T> comparator)
{
    return SemanticEquals(lhs, rhs, (l, r) => lhs.SequenceEqual(rhs, comparator));
}

List<Fruit> a, b;
return ConstraintChecker.SemanticSequenceEquals(a, b, new FruitEqualityComparer());
Gary Linscott
Indeed. If you're not using the genericity, don't use generics. You don't need to have the types U and V, so you shouldn't include them. In this example, SemanticSequenceEquals does not need to know that its arguments are actually List<Fruit>, as it only uses the IEnumerable<Fruit> functionality.
Joren
You're right that `U` and `V` could be done away with; I missed this various obvious refactoring. I still seem to have to specify `T` explicitly, though. Does your example (which omits the `T` specification from the `SemanticSequenceEquals` invocation) compile for you?
John Feminella
It compiles fine for me in VS2010, yes. I had to add a few `where T : class` specifiers though.
Gary Linscott