Не работает сравнение в Generics-методе ???
От: Аноним  
Дата: 20.07.10 20:24
Оценка:
Такой вот код:
    public class Point
    {
        public int X { get; set; }
        public int Y { get; set; }

        public static bool operator ==(Point op1, Point op2)
        {
            Console.WriteLine("Point.operator==");
            return op1.X == op2.X && op1.Y == op2.Y;
        }

        public static bool operator !=(Point op1, Point op2)
        {
            return !(op1 == op2);
        }
    }

    public class Test
    {
        public void Main()
        {
            Point A = new Point();
            A.X = 10;
            A.Y = 11;

            Point copyA = new Point();
            copyA.X = 10;
            copyA.Y = 11;

            Point B = new Point();
            B.X = 15;
            B.Y = 12;

            Console.WriteLine((A == copyA).ToString().ToUpper()); //true  - используется Point.operator==
            Console.WriteLine((A == B).ToString().ToUpper());     //false - используется Point.operator==

            GenericTest<Point>(A, copyA, B);
        }

        static void GenericTest<T>(T A, T copyA, T B)
            where T : class
        {
            Console.WriteLine((A == copyA).ToString().ToUpper()); //false
            Console.WriteLine((A == B).ToString().ToUpper());     //false
        }

        static void NonGenericTest(Point A, Point copyA, Point B)
        {
            Console.WriteLine((A == copyA).ToString().ToUpper()); //true  - используется Point.operator==
            Console.WriteLine((A == B).ToString().ToUpper());     //false - используется Point.operator==
        }
    }

Вопрос в том почему, для одних и тех-же значения вызывается в одном случае вызывается Point.operator==, а в другом object.operator== !!! Почему для метода с параметром типа, не вызывается Point.operator==
Re: Не работает сравнение в Generics-методе ???
От: Пельмешко Россия blog
Дата: 20.07.10 21:26
Оценка: 12 (2)
Здравствуйте, Аноним, Вы писали:

А>Вопрос в том почему, для одних и тех-же значения вызывается в одном случае вызывается Point.operator==, а в другом object.operator== !!! Почему для метода с параметром типа, не вызывается Point.operator==


1. Переопределение оператора== — это возможность языка C# и чтобы он его применял, должно быть статически известно что слева и справа оператора == будут значения типа Point. В приведённом generic-коде такого знания нет, так как сравниваются два значения типа T, а не Point.
2. Проверка эквивалентности в .NET — довольно щепетильная тема, одного переопределения операторов в C# недостаточно. Стоит хотя бы обратить внимание на warning'и, которыми ругается компилятор на вышеприведённый код Если вкратце, то переопределяя операторы ==/!= следует так же переопределить метод object.Equals(object) и object.GetHashCode(), при этом желательно чтобы реализация .Equals() полагалась на реализацию оператора== (хотя это можно оспорить ), а так же возвращала false если входной аргумент является null или значением типа, не являющимся Point. Более того, если Point — структура, то следует реализовать интерфейс IEquatable<T>.
3. В generic-коде всё хитро Оператор== применять к аргументам типа T возможно только если на тип-параметр T наложено ограничение where T: class, при этом сравнение будет простым ссылочным сравнением, аналогично Object.ReferenceEquals(). Обобщённому коду совершенно неизвестно, что в подставляемых типах могут быть переопределены какие-то операторы и всё, что он может сделать — сравнивать ссылочно, так как из ограничения на тип-параметр известно, что сравниваться будут управляемые ссылки.
4. Чтобы правильно сравнивать или проверять эквивалентность значений в generic-коде следует воспользоваться экземплярами компараторов, возвращаемых свойствами EqualityComparer<T>.Default (для проверки эквивалентности) и Comparer<T>.Default (для сравнения).
5. Ваш код может выглядеть как-то так:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public static bool operator ==(Point a, Point b)
    {
        return ((object) a != null) && ((object) b != null) && (a.X == b.X) && (a.Y == b.Y);
    }

    public static bool operator !=(Point op1, Point op2)
    {
        return !(op1 == op2);
    }

    public override bool Equals(object obj)
    {
        var p = obj as Point;
        return (p != null) && (this == p);
    }

    public override int GetHashCode() { return X ^ Y; }
}

public class Test
{
    public static void Main()
    {
        Point A = new Point();
        A.X = 10;
        A.Y = 11;

        Point copyA = new Point();
        copyA.X = 10;
        copyA.Y = 11;

        Point B = new Point();
        B.X = 15;
        B.Y = 12;

        Console.WriteLine((A == copyA).ToString().ToUpper()); //true  - используется Point.operator==
        Console.WriteLine((A == B).ToString().ToUpper());     //false - используется Point.operator==

        GenericTest<Point>(A, copyA, B);
    }

    static void GenericTest<T>(T A, T copyA, T B)
    {
        var comparer = EqualityComparer<T>.Default;
        Console.WriteLine((comparer.Equals(A, copyA)).ToString().ToUpper()); //true
        Console.WriteLine((comparer.Equals(A, B)).ToString().ToUpper());     //false
    }

    static void NonGenericTest(Point A, Point copyA, Point B)
    {
        Console.WriteLine((A == copyA).ToString().ToUpper()); //true  - используется Point.operator==
        Console.WriteLine((A == B).ToString().ToUpper());     //false - используется Point.operator==
    }
}
Re[2]: Не работает сравнение в Generics-методе ???
От: _FRED_ Черногория
Дата: 21.07.10 05:07
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>…Более того, если Point — структура, то следует реализовать интерфейс IEquatable<T>.


А в классах, значит, IEquatable<T> не нужен?

П>4. Чтобы правильно сравнивать или проверять эквивалентность значений в generic-коде следует воспользоваться экземплярами компараторов, возвращаемых свойствами EqualityComparer<T>.Default (для проверки эквивалентности) и Comparer<T>.Default (для сравнения).


Ещё лучше: добавить перегрузку, принимающую компаратор.
Help will always be given at Hogwarts to those who ask for it.
Re: Не работает сравнение в Generics-методе ???
От: nikov США http://www.linkedin.com/in/nikov
Дата: 21.07.10 05:22
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Вопрос в том почему, для одних и тех-же значения вызывается в одном случае вызывается Point.operator==, а в другом object.operator== !!! Почему для метода с параметром типа, не вызывается Point.operator==


В C# обычно разрешение перегрузок для операторов происходит статически, на основе compile-time типов. Если нужна диспетчеризация на основе runtime-типов объектов, то надо пользоваться виртуальными методами или реализацией интерфейсов. При использовании generics придётся указать соответствующий базовый класс или интерфейс в констрейнтах у типа-параметра.

Но из этих правил есть одно исключение. Разрешение большинства операторов можно перенести в runtime, если тип одного из операндов dynamic. Но за это придется заплатить некоторую цену. Во-первых, это влияет на производительность (несмотря на то, что в DLR применяется хитроумное кеширование). Во-вторых, во время компиляции отключается любой контроль за наличием у типа соответствующих операторов подходящего типа (если что-то пойдет не так, будет исключение в runtime).

static void GenericTest<T>(T A, T copyA, T B)
    where T : class
{
    Console.WriteLine((A == (dynamic)copyA).ToString().ToUpper()); //false
    Console.WriteLine((A == (dynamic)B).ToString().ToUpper());     //false
}
Re[3]: Не работает сравнение в Generics-методе ???
От: Пельмешко Россия blog
Дата: 21.07.10 09:09
Оценка: 36 (1)
Здравствуйте, _FRED_, Вы писали:

_FR>Здравствуйте, Пельмешко, Вы писали:


П>>…Более того, если Point — структура, то следует реализовать интерфейс IEquatable<T>.


_FR>А в классах, значит, IEquatable<T> не нужен?


Я так и знал, что кто-нибудь докопается

Писал ответ в пол второго после почти 12 часового рабочего дня, решил что дорогому Анониму не обязательно знать, что IEquatable<T> следует так же реализовывать в неизменяемых ref-типах с семантикой значений (System.String и System.Version как пример).

Упомянул структуры, так как считаю основным бенефитом IEquatable<T> отсутствие боксинга обоих сравниваемых операндов value-типа при сравнении через EqualityComparer<T>.Default, а то что он ликвидирует проверку типа и downcast, требуемые в object.Equals(object) — это уже не так важно имхо
Re[4]: Не работает сравнение в Generics-методе ???
От: _FRED_ Черногория
Дата: 21.07.10 09:31
Оценка: +1
Здравствуйте, Пельмешко, Вы писали:

П>>>…Более того, если Point — структура, то следует реализовать интерфейс IEquatable<T>.

_FR>>А в классах, значит, IEquatable<T> не нужен?
П>Я так и знал, что кто-нибудь докопается

П>Писал ответ в пол второго после почти 12 часового рабочего дня, решил что дорогому Анониму не обязательно знать, что IEquatable<T> следует так же реализовывать в неизменяемых ref-типах с семантикой значений (System.String и System.Version как пример).


Угу.

П>Упомянул структуры, так как считаю основным бенефитом IEquatable<T> отсутствие боксинга обоих сравниваемых операндов value-типа при сравнении через EqualityComparer<T>.Default, а то что он ликвидирует проверку типа и downcast, требуемые в object.Equals(object) — это уже не так важно имхо


Совершенно точно! Конечно, для классов IEquatable<> не играет такой роли, как для структур, но реализовывает его там всё равно нужно.
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Не работает сравнение в Generics-методе ???
От: Аноним  
Дата: 22.07.10 11:26
Оценка:
Здравствуйте, nikov, Вы писали:

N>Разрешение большинства операторов можно перенести в runtime, если тип одного из операндов dynamic. Но за это придется заплатить некоторую цену. Во-первых, это влияет на производительность (несмотря на то, что в DLR применяется хитроумное кеширование). Во-вторых, во время компиляции отключается любой контроль за наличием у типа соответствующих операторов подходящего типа (если что-то пойдет не так, будет исключение в runtime).


N>
N>static void GenericTest<T>(T A, T copyA, T B)
N>    where T : class
N>{
N>    Console.WriteLine((A == (dynamic)copyA).ToString().ToUpper()); //false
N>    Console.WriteLine((A == (dynamic)B).ToString().ToUpper());     //false
N>}
N>


А почему бы не сделать так:
static void GenericTest(dynamic A, dynamic copyA, dynamic B)
{
    Console.WriteLine((A == copyA).ToString().ToUpper()); 
    Console.WriteLine((A == B).ToString().ToUpper());     
}

Если не забыть про возможность получить исключение в runtime, что измениться
Re[3]: Не работает сравнение в Generics-методе ???
От: nikov США http://www.linkedin.com/in/nikov
Дата: 22.07.10 11:27
Оценка:
Здравствуйте, Аноним, Вы писали:

А>А почему бы не сделать так:

А>
А>static void GenericTest(dynamic A, dynamic copyA, dynamic B)
А>{
А>    Console.WriteLine((A == copyA).ToString().ToUpper()); 
А>    Console.WriteLine((A == B).ToString().ToUpper());     
А>}
А>


Можно и так.
Re[4]: Не работает сравнение в Generics-методе ???
От: Аноним  
Дата: 23.07.10 11:43
Оценка:
Здравствуйте, nikov, Вы писали:

N>Здравствуйте, Аноним, Вы писали:


А>>А почему бы не сделать так:

А>>
А>>static void GenericTest(dynamic A, dynamic copyA, dynamic B)
А>>{
А>>    Console.WriteLine((A == copyA).ToString().ToUpper()); 
А>>    Console.WriteLine((A == B).ToString().ToUpper());     
А>>}
А>>


N>Можно и так.


Подводя итог, можно-ли сказать, что dynamic это то-же, что и var, только при использовании var разрешение происходит статически, во время компиляции, а при использовании dynamic — динамически, во время выполнения. Причем алгоритмы разрешения одни и те-же, разница только в том когда они применяются
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.