Неявный метод группового преобразования Гоча (часть 2) - PullRequest
14 голосов
/ 20 января 2012

Упрощено с этот вопрос и избавлено от возможного аффекта от LinqPad (без оскорблений), простого консольного приложения, подобного этому:

public class Program
{
    static void M() { }    
    static void Main(string[] args)
    {
        Action a = new Action(M);
        Delegate b = new Action(M);
        Console.WriteLine(a == b);      //got False here
        Console.Read();
    }        
}

Оператор ceq выдает «ложь» в приведенном выше коде (подробности см. В исходном вопросе). Итак, мои вопросы:

(1) Почему == переводится на ceq вместо call Delegate Equals?

Здесь меня не волнует (не) перенос между делегатом и действием. В самом последнем случае при оценке a == b a имеет тип Action, а b является Delegate. Из спецификации:

7.3.4 Разрешение перегрузки бинарного оператора

Операция вида x op y, где op - это перегружаемый двоичный оператор, x - выражение типа X, а y является выражением типа Y, обрабатывается следующим образом:

• Набор пользовательских операторов-кандидатов, предоставляемых X и Y для оператор операции op (x, y) определен. Набор состоит из объединение операторов-кандидатов, предоставляемых X и кандидатом операторы, предоставляемые Y, каждый из которых определяется с использованием правил §7.3.5. Если X и Y имеют одинаковый тип, или если X и Y получены из общего базовый тип, то общие операторы-кандидаты встречаются только в сочетании установить один раз.

• Если набор возможных пользовательских операторов не пустым, то это становится набором операторов-кандидатов для операция. В противном случае предопределенный бинарный оператор оп реализации, включая их поднятые формы, становятся набором операторы-кандидаты на операцию. Предопределенные реализации данного оператора указаны в описании оператора (§7.8–7.12).

• Правила разрешения перегрузки согласно §7.5.3 применяется к набору операторов-кандидатов для выбора лучшего оператора относительно списка аргументов (x, y), и этот оператор становится результат процесса разрешения перегрузки. Если разрешение перегрузки не удается выбрать лучший оператор, возникает ошибка времени привязки.

7.3.5. Пользовательские операторы-кандидаты

Учитывая тип T и оператор операции op (A), где op - это перегружаемый оператор, а A - список аргументов, набор возможных пользовательских операторов, предоставляемых T для оператора op (A) определяется следующим образом:

• Определить тип T0. Если T является обнуляемым типом, T0 является его базовым типом, в противном случае T0 равно T.

• Для всех операторов операторов в T0 и всех снятых формы таких операторов, если хотя бы один оператор применим (§7.5.3.1) относительно списка аргументов A, то множество операторы-кандидаты состоят из всех таких применимых операторов в T0.

• В противном случае, если T0 является объектом, набор операторов-кандидатов будет пустым.

• В противном случае набор операторов-кандидатов, предоставляемый T0, является набором операторов-кандидатов, предоставляемых прямым базовым классом T0, или эффективный базовый класс T0, если T0 является параметром типа.

Из спецификации, a и b имеют один и тот же базовый класс Delegate, очевидно, здесь должно применяться правило оператора ==, определенное в Delegate (оператор == вызывает Delegate.Equals по существу). Но теперь похоже, что список кандидатов пользовательских операторов пуст и, наконец, применяется Object ==.

(2) Должен ли код FCL подчиняться спецификации языка C #? Если нет, мой первый вопрос не имеет смысла, потому что что-то специально рассматривается. И тогда мы можем ответить на все эти вопросы, используя: «о, это особый подход в FCL, они могут делать то, что мы не можем. Спецификация для сторонних программистов, не будь глупой».

Ответы [ 4 ]

5 голосов
/ 25 января 2012

Компилятор работает очень по-разному и необычно с делегатами.Есть много неявных обработок. Обратите внимание, что правило «общего базового типа» в этом руководстве применяется к «пользовательским операторам».Делегаты являются внутренними и системными. Например, вы можете написать Action a = M; вместо Action a = new Action(M);.И вы можете добавить a += M; после этого.Проверьте, что происходит в CIL, это интересно с первого раза.

Более того: сравнивать делегатов опасно и нетривиально.Каждый делегат на самом деле является многоадресным делегатом.Вы можете добавить несколько указателей функций к одному и тому же делегату.Является ли делегат [L(); M(); N();] равным делегату [M();]?Указатель на функцию содержит экземпляр класса (например, метод).[a.M();] равно [b.M();]?Все, что зависит от случая, и реализация сравнения требует пошагового прохождения по списку вызовов.

Делегирование наследования от общего базового типа Делегат неявно, и с этой проблемой вы можете столкнуться в других сценариях, например, общее ограничение: вы не можете указатьДелегировать как ограничение универсального параметра T. Здесь компилятор явно отклоняет это.То же самое с созданием ваших собственных классов, унаследованных от Delegate.

Это ответ на оба вопроса - «Delegate» - это не просто FCL, он тесно связан с компилятором.Если вы действительно хотите поведение компаратора делегата Microsoft - просто вызовите явно Equals(a, b)

4 голосов
/ 25 января 2012

Существует два типа операторов: пользовательские операторы и предопределенные операторы. Раздел 7.3.5 «Пользовательские операторы-кандидаты» не применяется к предопределенным операторам. Например, операторы на decimal выглядят как пользовательские операторы в декомпиляторе, но C # обрабатывает их как предопределенные операторы и применяет к ним числовое продвижение (числовое продвижение не применяется к пользовательским операторам).

Раздел 7.10.8 «Операторы равенства делегатов» определяет operator ==(Delegate, Delegate) как предопределенный оператор, поэтому я думаю, что все правила, касающиеся пользовательских операторов, не применяются к этому оператору (хотя это не 100% ясно в спецификации, так как в этом случае предопределенный оператор не применяется всякий раз, когда пользовательский оператор будет).

Every delegate type implicitly provides the following predefined comparison operators: 
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y); 

Но System.Delegate сам по себе не считается типом делегата, поэтому единственным кандидатом для разрешения перегрузки является operator ==(object, object).

3 голосов
/ 28 декабря 2014

предупреждение CS0253: возможно непреднамеренное сравнение эталонов; чтобы получить сравнение значений, приведите правую сторону к типу «System.Action»

Это предупреждение, которое вы получаете за этот код C #. Не игнорируйте это предупреждение, команда C # прекрасно понимала, что код, сгенерированный для этого сравнения, был неожиданным. У них не было для генерации этого кода, они могли бы легко сделать то, что вы ожидали. Как этот код делает:

Module Module1
    Sub M()
    End Sub

    Sub Main()
        Dim a = New Action(AddressOf M)
        Dim b = DirectCast(New Action(AddressOf M), [Delegate])
        Console.WriteLine(a = b)      ''got True here
        Console.Read()
    End Sub
End Module

, который генерирует почти тот же MSIL, за исключением того, что вместо ceq вы получаете:

 IL_001d:  call bool [mscorlib]System.Delegate::op_Equality(class [mscorlib]System.Delegate,
                                                            class [mscorlib]System.Delegate)

Что, как вы и надеялись, будет делать код C #. Это был код VB.NET, если вы его не узнали. В противном случае причина в том, что Microsoft поддерживает два основных управляемых языка, хотя они имеют очень похожие возможности. Но с очень разными вариантами юзабилити. Всякий раз, когда было более одного способа генерации кода, команда C # последовательно выбирала для производительность , команда VB.NET последовательно для удобства .

И производительность, безусловно, является ключевым моментом, сравнение объектов делегатов стоит дорого . Правила изложены в Ecma-335, раздел II.14.6.1. Но вы можете сами объяснить это, есть много проверок, чтобы сделать. Необходимо проверить, совместим ли целевой объект делегата. И для каждого аргумента он должен проверить, является ли значение конвертируемым. За счет того, что команда C # не хочет прятаться.

И нет, вы получаете предупреждение, чтобы напомнить вам, что они сделали не интуитивный выбор. .

0 голосов
/ 20 января 2012

Ключевым моментом здесь является то, что оператор == и метод Equals для типа Delegate - это две разные вещи. Для ссылочных типов == проверяет, указывают ли обе ссылки на один и тот же объект, если только оператор == не переопределен (см .: == Оператор (C #) ).

Поскольку вы создаете два разных объекта Action, даже если они внутренне вызывают один и тот же метод, они являются разными объектами в разных местах в памяти и не имеют значения или типа string, поэтому == в этом случае ReferenceEquals и не вызывает метод Delegate.Equals, который был переопределен, чтобы увидеть, делают ли два объекта одно и то же. Для ссылочных типов, отличных от string, это поведение по умолчанию == или Equals.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...