Question

I've read various questions similar to mine but none of them seem address my issue.

I've a type like this:

class MyObject<T> : IEquatable<MyObject<T>> { // no generic constraints
  private readonly string otherProp;
  private readonly T value;

  public MyObject(string otherProp, T value)
  {
    this.otherProp = otherProp;
    this.value = value;
  }

  public string OtherProp { get { return this.otherProp; } }

  public T Value { get { return this.value; } }

  // ...

  public bool Equals(MyObject<T> other)
  {
    if (other == null)
    {
       return false;
    }

    return this.OtherProp.Equals(other.OtherProp) && this.Value.Equals(other.Value);
  }
}

When T is a scalar as MyObject<int> equality works correctly, but when I defined something like MyObject<IEnumerable<int>> equality fails.

The reason is that when T is IEnumerable<T> I should call this.Value.SequenceEqual(other.Value).

Handling this difference bloats Equals(MyObject<T>) with too LOC of type check and reflection (for me, leading to a violation of SOLID/SRP).

I was not able to found this specific case in MSDN guidelines, so if anyone already faced this problem; it would be great if this knowledge can be shared.

Edit: Alternative

To K.I.S.S., I'm wondering to doing something similar:

class MyObject<T> : IEquatable<MyObject<T>> {
  private readonly IEnumerable<T> value;

  // remainder omitted
}

In this way the implementation of Equal will be a lot simple. And when I need just one value I've a collection of 1 item. Obviously T will not be an enumerable (but the data structure is private so there's no problem).

Was it helpful?

Solution 2

You could have your MyObject<T> take an equality comparer for your type T and use that:

class MyObject<T> : IEquatable<MyObject<T>>
{
    private readonly IEqualityComparer<T> comparer;
    public MyObject(string otherProp, T value, IEqualityComparer<T> comparer)
    {
        this.comparer = comparer;
    }
    public MyObject(string otherProp, T value)
        : this(otherProp, value, EqualityComparer<T>.Default)
    {
    }

    public bool Equals(MyObject<T> other)
    {
        return OtherProp.Equals(other.OtherProp) && comparer.Equals(this.Value, other.Value);
    }
}

Then for IEnumerable<T> you can use a comparer which compares the sequences instead of the references. You might want to make use of factory methods to create your objects to ensure the same comparer type is used for the same T to ensure equality remains symmetric.

OTHER TIPS

In case when T is IEnumerable<T>, your code compares two references of IEnumerable<T> and, of course, those references may be not equal. Actually, you'll get this behavior, when T will be any reference type without overridden Equals method.

If you don't want to compare references here, you should write a code, which will compare these sequences by their content. However, you should note, that sequence may be endless.

No reflection is needed. One option is to check if value is IEnumerable or not in your Equals method:

IEnumerable e = other.Value as IEnumerable;

if(e != null){
 // use  SequenceEqual
}else{
 // use Equals
}

this is the case where you should take help of helper class , your MyObject type may make use of individual EqualityChecker.

I would say implement a strategy pattern with static factory method will simplify your design.

something like below

class MyObject<T> : IEquatable<MyObject<T>>
{ // no generic constraints
    private readonly string otherProp;
    private readonly T value;

    public MyObject(string otherProp, T value)
    {
        this.otherProp = otherProp;
        this.value = value;
    }

    public string OtherProp { get { return this.otherProp; } }

    public T Value { get { return this.value; } }

    // ...

    public bool Equals(MyObject<T> other)
    {
        if (other == null)
        {
            return false;
        }

     var cheker =   EqualityChekerCreator<T>.CreateEqualityChecker(other.Value);

     if (cheker != null)
         return cheker.CheckEquality(this.Value, other.value);

        return this.OtherProp.Equals(other.OtherProp) && this.Value.Equals(other.Value);
    }
}

public static class  EqualityChekerCreator<T>
{
    private static IEqualityCheker<T> checker;

    public static IEqualityCheker<T>  CreateEqualityChecker(T type)
    {
        var currenttype = type as IEnumerable<T>;

        if(currenttype!=null)
            checker = new SequenceEqualityChecker<T>();

        return checker;
    }
}

public  interface  IEqualityCheker<in T>
{
    bool CheckEquality(T t1, T t2);
}

public class SequenceEqualityChecker <T> : IEqualityCheker<T>
{
    #region Implementation of IEqualityCheker<in T>

    public bool CheckEquality(T t1, T t2)
    {
        // implement method here;
    }

    #endregion
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top