简体   繁体   中英

Comparing two arrays in a generic .Equals method

Please see the code below:

 public virtual bool Equals(T other)
            {
                if (other == null)
                    return false;
                Type t = GetType();
                Type otherType = other.GetType();
                if (t != otherType)
                    return false;
                FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                foreach (FieldInfo field in fields)
                {
                    object value1 = field.GetValue(other);
                    object value2 = field.GetValue(this);
                    if (value1 == null)
                    {
                        if (value2 != null)
                           return false;
                    }
                    else if (!value1.Equals(value2))
                        return false;
                }
                return true;
            }

Say value1 and value2 are arrays of strings. The code above returns false even if the arrays have the same contents. How can I ensure that true is returned if they contain the same contents?

I have looked here: How to compare arrays in C#? and tried this:

bool isEqual = Enumerable.SequenceEqual(value1, value2);

The compiler error is: SequenceEqual cannot be inferred from usage.

Part of the problem is that field.GetValue(other) returns an object . You'll likely find that using recursive reflection to compare untyped objects will frustrate you.

In order to make this work you'd have to determine whether the field is a collection, and if it is, compare the contents of the collection instead. Then what if one of collections contains a collection? This is doable, but it's a headache.

Whatever this object is that you're trying to compare, it's much simpler if you implement IEquatable<T> or define an IEqualityComparer<T> for the types you want to compare. Your Equals method then checks equality explicitly for the various members. If those members are reference types, you define equality for those as well. Then, if those types have more nested properties you don't have drill down into all of those nested properties and compare their properties.

What this ultimately requires is that your method "knows" what types it's comparing so that it knows exactly how to compare them. If you're using reflection to figure out what the properties are and then trying to check them for equality, it's possible, but it's difficult and potentially broken by future changes.

Here's an example using a few classes with nested properties.

public class Foo : IEquatable<Foo>
{
    public int FooValue { get; set; }
    public List<Bar> Bars { get; set; }

    public bool Equals(Foo other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        if (FooValue != other.FooValue) return false;
        if (Bars == null ^ other.Bars == null) return false;
        return Bars == null || Bars.SequenceEqual(other.Bars);
    }
}

public class Bar : IEquatable<Bar>
{
    public int BarValue { get; set; }
    public List<FooBar> FooBars { get; set; }

    public bool Equals(Bar other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        if( BarValue != other.BarValue) return false;
        if (FooBars == null ^ other.FooBars == null) return false;
        return FooBars==null || FooBars.SequenceEqual(other.FooBars);
    }
}

public class FooBar : IEquatable<FooBar>
{
    public int FooBarValue { get;  set; }

    public bool Equals(FooBar other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return FooBarValue == other.FooBarValue;
    }
}

Each class with a nested collection property uses SequenceEqual to determine whether the collections match. But how does it figure out whether individual items in those collections are equal to each other? It doesn't. It assumes that those types will be able to to determine equality.

Now if the conditions that make two instances of a class equatable change, the details can be isolated to that class.

Suppose I can't modify one of these classes to support IEquatable<T> , or for some reason it doesn't make sense to. Perhaps equality is determined differently in different contexts. Maybe a class has an ID property and in some context all I care about is that if two objects have the same ID , they're equal.

In that case I could move that equality comparison into its own class and only use it when I want to. It's no longer "built in" to the class.

public class FooBar 
{
    public int FooBarValue { get; set; }
}

public class FooBarComparer : IEqualityComparer<FooBar>
{
    public bool Equals(FooBar x, FooBar y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        return x.FooBarValue == y.FooBarValue;
    }

    public int GetHashCode(FooBar obj)
    {
        return obj.GetHashCode();
    }
}

Now when I use SequenceEqual on two collections of FooBar I would do this:

FooBars.SequenceEqual(other.FooBars, new FooBarComparer())

Another benefit of this is that you can re-use equality comparisons. Suppose you have two different classes with a list of Bar . Now both classes don't have to inspect Bar and its nested properties to determine equality, which would duplicate code. That equality check is located elsewhere and can be shared by both when that's desirable.

The issue most likely has to do with referential equals vs value equality. If you compare 2 objects that don't override Equals you are comparing that they are the same item not the same value.

The SequenceEqual error you are seeing has to do with the compiler not being sure of the type you are using to compare.
https://msdn.microsoft.com/en-us/library/bb348567(v=vs.110).aspx

You can specify the type you want to use to check equality

bool isEqual = Enumerable.SequenceEqual<T>(value1, value2);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM