Показаны сообщения с ярлыком Tuple. Показать все сообщения
Показаны сообщения с ярлыком Tuple. Показать все сообщения

четверг, 18 августа 2011 г.

IStructuralEquatable & IStructuralComparable


IStructuralEquatable & IStructuralComparable
Редакция статьи от 24.04.2013
   С выходом .net 4, в нем помимо всего прочего, добавилось два интерфейса - IStructuralEquatable & IStructuralComparable, которые явно (explicit) реализуются кортежами (Tuple) и массивами (Array).
IStructuralEquatable
 
Позволяет структурно сравнить между собой два объекта.
public interface IStructuralEquatable
{
   bool Equals(object other,System.Collections.IEqualityComparer comparer);
   int GetHashCode(System.Collections.IEqualityComparer comparer);
}

   К примеру, необходимо сравнить два Int32 массива. Структура массивов, представляет из себя последовательность значений, соответственно, чтобы массивы считались равными нужно сравнить между собой значения массивов, то есть первый элемент первого массива должен быть равен первому элементу второго массива, и так далее. Если попробовать выполнить эту задачу в лоб, то столкнёмся с некоторыми трудностями, так как по умолчанию поведение метода System.Object.Equals проверяет, указывают ли ссылки на один и тот же объект, и System.Array не переопределяет данный метод.
using System;

namespace Structual
{
    class Program
    {
        static void Main()
        {
            int[] array = { 1, 1, 8 };
            int[] anotherArray = { 1, 1, 8 };

            Console.WriteLine(array.Equals(anotherArray));// False
        }
    }
}

   Чтобы структурно сравнить два массива, надо создать класс, реализующий IEqualityComparer, этот интерфейс используется для сравнения элементов массивов друг с другом. К примеру если в сравниваемых массивах по три элемента то этот интерфейс будет использован трижды, с его помощью в начале будут сравнены два первых элемента массивов, затем два вторых и два третьих.
using System;
using System.Collections;

namespace Structual
{
    public sealed class ObjectEqualityComparer : IEqualityComparer
    {
        public new bool Equals(object itemFromArray1, object itemFromArray2)
        {
            return itemFromArray1.Equals(itemFromArray2);
        }
        public int GetHashCode(object obj)
        {
            return obj.GetHashCode();
        }
    }
    class Program
    {
        static void Main()
        {
            int[] array = { 1, 1, 8 };
            int[] anotherArray = { 1, 1, 8 };
            IStructuralEquatable firstArreyForEqual = array;
            Console.WriteLine(firstArreyForEqual.Equals(anotherArray, new ObjectEqualityComparer())); //True 
        }
    }
}

Обратите внимание на строку кода
IStructuralEquatable firstArreyForEqual = array;

   Интерфейс IStructuralEquatable реализован в System.Array как Explicit Interface и мы не можем обратиться к нему напрямую через переменную array.
   Так же, постоянно реализовывать ObjectEqualityComparer нет необходимости, так как в .net уже есть специальный класс  StructuralEqualityComparer, если воспользоваться дизассемблером и взглянуть на его код, то увидим следующее:
internal class StructuralEqualityComparer : IEqualityComparer
{
    // Methods
    public bool Equals(object x, object y)
    {
        if (x != null)
        {
            IStructuralEquatable equatable = x as IStructuralEquatable;
            if (equatable != null)
            {
                return equatable.Equals(y, this);
            }
            return ((y != null) && x.Equals(y));
        }
        if (y != null)
        {
            return false;
        }
        return true;
    }

    public int GetHashCode(object obj)
    {
        if (obj == null)
        {
            return 0;
        }
        IStructuralEquatable equatable = obj as IStructuralEquatable;

        if (equatable != null)
        {
            return equatable.GetHashCode(this);
        }
        return obj.GetHashCode();
    }
}
   Как можно заметить функциональность у нашего ObjectEqualityComparer и StructuralEqualityComparer идентична (за исключением проверок, которые необходимы для кода использующегося в реальных коммерческих приложениях), можно переписать предыдущий пример.
  Так как класс StructuralEqualityComparer имеет уровень доступа Internal, получить его можно через статический класс StructuralComparisons:
using System;
using System.Collections;

namespace Structual
{
    class Program
    {
        static void Main()
        {
            int[] array = { 1, 1, 8 };
            int[] anotherArray = { 1, 1, 8 };
            IStructuralEquatable firstArreyForEqual = array;
            Console.WriteLine(firstArreyForEqual.Equals(anotherArray, StructuralComparisons.StructuralEqualityComparer)); //True 
        }
    }
}

Обратите внимание на код в StructuralEqualityComparer
IStructuralEquatable equatable = x as IStructuralEquatable;
if (equatable != null)
{
    return equatable.Equals(y, this);
}

   Он учитывает, является ли сравниваемый элемент сам массивом, кортежем или любым другим типом реализующим IStructuralEquatable, к примеру:
using System;
using System.Collections;

namespace Structual
{
    class Program
    {
        static void Main()
        {
            Object[] array = { 1, new[] {1,2,3} };
            Object[] anotherArray = { 1, new[] { 1, 2, 3 } };
            IStructuralEquatable firstArreyForEqual = array;
            Console.WriteLine(firstArreyForEqual.Equals(anotherArray, StructuralComparisons.StructuralEqualityComparer)); //True 
            ((int[]) anotherArray[1])[2] = 5;
            Console.WriteLine(firstArreyForEqual.Equals(anotherArray, StructuralComparisons.StructuralEqualityComparer)); //False
        }
    }
}
   Но как же происходит процесс сравнения?  Посмотрим через дизассемблер на реализацию интерфейса IStructuralEquatable в System.Array:
bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
{
    if (other == null)
    {
        return false;
    }
    if (!object.ReferenceEquals(this, other))
    {
        Array array = other as Array;
        if ((array == null) || (array.Length != this.Length))
        {
            return false;
        }

        for (int i = 0; i < array.Length; i++)
        {
            object x = this.GetValue(i);
            object y = array.GetValue(i);
            if (!comparer.Equals(x, y))
            {
                return false;
            }
        }
    }
    return true;
}

int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
{
    if (comparer == null)
    {
        throw new ArgumentNullException("comparer");
    }
    int num = 0;
    for (int i = (this.Length >= 8) ? (this.Length - 8) : 0; i < this.Length; i++)
    {
        num = CombineHashCodes(num, comparer.GetHashCode(this.GetValue(0)));
    }
    return num;
}
   Если в двух словах описать реализацию IStructuralEquatable.Equals в System.Array, то она поочередно сравнивает все элементы в массивах и если хоть один из них не равен друг другу, то и массивы не равны.
IStructuralComparable

Поддерживает структурное сравнение объектов коллекции.
   Все выше сказанное так же относится и к IStructuralComparable, он так же реализован в
System.Array и Sytem.Tuple<T> как Explicit Interface. И для него так же реализован класс StructuralEqualityComparer, который можно получить через статический класс StructuralComparisons, для сравнения массивов и кортежей.
Рассмотрим на примере:

using System;
using System.Collections;

namespace Structual
{
    internal class Program
    {
        private static void Main()
        {
            int[] array = { 1, 1, 8 };
            int[] anotherArray = { 1, 1, 8 };

            IStructuralComparable firstArrey = array;

            Console.WriteLine(firstArrey.CompareTo(anotherArray, StructuralComparisons.StructuralComparer));// результат 0, означает что массивы равны
            anotherArray[2] = 7;
            Console.WriteLine(firstArrey.CompareTo(anotherArray, StructuralComparisons.StructuralComparer));// результат 1, означает что firstArray больше чем anotherArray
            anotherArray[2] = 9;
            Console.WriteLine(firstArrey.CompareTo(anotherArray, StructuralComparisons.StructuralComparer));// результат -1, означает что firstArray меньше чем anotherArray
        }
    }
}

Исследуем с помощью дизассемблера реализацию  IStructuralComparable.CompareTo в System.Array:
int IStructuralComparable.CompareTo(object other, IComparer comparer)
{
    if (other == null)
        return 1;

    Array array = other as Array;
    if (array == null || this.Length != array.Length)
        throw new ArgumentException(Environment.GetResourceString("ArgumentException_OtherNotArrayOfCorrectLength"), "other");
   
    int index = 0;
    int num;
    for (num = 0; index < array.Length && num == 0; ++index)
    {
        object x = this.GetValue(index);
        object y = array.GetValue(index);
        num = comparer.Compare(x, y);
    }
    return num;
}

Как видно из кода, метод пытается найти первое расхождение, если расхождений не обнаружено - объекты равны.

Теперь рассмотрим под дизассемблером Код StructuralComparer:

internal class StructuralComparer : IComparer
{
    public int Compare(object x, object y)
    {
        if (x == null)
        {
            if (y != null)
                return -1;
            else
                return 0;
        }
        else
        {
            if (y == null)
                return 1;
            IStructuralComparable structuralComparable = x as IStructuralComparable;
            if (structuralComparable != null)
                return structuralComparable.CompareTo(y, (IComparer)this);
            else
                return Comparer.Default.Compare(x, y);
        }
    }
}

Довольно интересна следующая часть кода:
IStructuralComparable structuralComparable = x as IStructuralComparable;
if (structuralComparable != null)
    return structuralComparable.CompareTo(y, (IComparer)this);
  Это означает, если элемент массива, сам является массивом, кортежем или любым другим типом реализующим IStructuralComparable, то сравнивание будет это учитывать.
К примеру:
using System;
using System.Collections;

namespace Structual
{
    internal class Program
    {
        private static void Main()
        {
            object[] array = { 1, new[] {1,2,3}};
            object[] anotherArray = { 1, new[] { 1, 2, 3 } };

            IStructuralComparable firstArrey = array;

            Console.WriteLine(firstArrey.CompareTo(anotherArray, StructuralComparisons.StructuralComparer));// результат 0, означает что массивы равны
            ((int[])anotherArray[1])[2] = 2;
            Console.WriteLine(firstArrey.CompareTo(anotherArray, StructuralComparisons.StructuralComparer));// результат 1, означает что firstArray больше чем anotherArray
            ((int[])anotherArray[1])[2] = 4;
            Console.WriteLine(firstArrey.CompareTo(anotherArray, StructuralComparisons.StructuralComparer));// результат -1, означает что firstArray меньше чем anotherArray
        }
    }
}

   На этом все, удачи вам и спасибо что прочитали мою статью. Буду рад оставленным комментариям и новым постоянным читателям.

Александр Кобелев aka Megano