Создание двух экземпляров делегата для одного и того же анонимного метода не равны - PullRequest
12 голосов
/ 14 сентября 2009

Рассмотрим следующий пример кода:

static void Main(string[] args)
{
   bool same = CreateDelegate(1) == CreateDelegate(1);
}

private static Action CreateDelegate(int x)
{
   return delegate { int z = x; };
}

Вы могли бы представить, что два экземпляра делегата будут сравниваться, чтобы быть равными, так же, как и при использовании старого метода с именованным методом (new Action (MyMethod)). Они не сравниваются, чтобы быть равными, потому что .NET Framework предоставляет скрытый экземпляр закрытия для каждого экземпляра делегата. Поскольку каждый из этих двух экземпляров делегатов имеет свои свойства Target, настроенные для отдельного скрытого экземпляра, они не сравниваются. Одно из возможных решений для сгенерированного IL для анонимного метода, чтобы сохранить текущий экземпляр (этот указатель) в цели делегата. Это позволит делегатам сравнивать правильно, а также помогает с точки зрения отладчика, поскольку вы увидите, что ваш класс является целевым, а не скрытым классом.

Подробнее об этой проблеме вы можете прочитать в сообщении об ошибке, которое я отправил в Microsoft. В отчете об ошибке также приведен пример того, почему мы используем эту функцию и почему мы считаем, что ее следует изменить. Если вы считаете, что это также является проблемой, помогите поддержать ее, предоставив оценку и подтверждение.

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489518

Можете ли вы увидеть возможные причины, по которым функциональность не должна изменяться? Считаете ли вы, что это лучший способ решить проблему, или вы рекомендуете выбрать другой путь?

Ответы [ 5 ]

19 голосов
/ 14 сентября 2009

Я не очень склонен думать, что это «ошибка». Более того, кажется, что вы предполагаете какое-то поведение в CLR, которое просто не существует.

Важно понимать, что вы возвращаете новый анонимный метод (и инициализируете новый класс замыкания) каждый раз, когда вызываете метод CreateDelegate. Похоже, вы экспериментируете с ключевым словом delegate, чтобы использовать какой-то пул для анонимных методов внутри страны. CLR, конечно, не делает этого. Делегат анонимного метода (как с лямбда-выражением) создается в памяти каждый раз, когда вы вызываете метод, и, поскольку оператор равенства действительно сравнивает ссылки в этой ситуации, это ожидаемый результат для возврат false.

Хотя предложенное вами поведение может иметь определенные преимущества в определенных контекстах, его реализация, вероятно, будет довольно сложной и, скорее всего, приведет к непредсказуемым сценариям. Я думаю, что текущее поведение генерации нового анонимного метода и делегата при каждом вызове является правильным, и я подозреваю, что это отклик, который вы также получите о Microsoft Connect.

Если вы очень настаиваете на том, чтобы поведение, которое вы описали в своем вопросе, всегда есть опция , запоминающая вашу CreateDelegate функцию, которая гарантирует, что один и тот же делегат будет возвращаться каждый раз для те же параметры. Действительно, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которой Microsoft не рассматривала возможность его реализации в CLR.

4 голосов
/ 14 сентября 2009

Я не знаю о специфических деталях этой проблемы в C #, но я работал над эквивалентной функцией VB.Net, которая работает аналогично

Суть в том, что это поведение "По замыслу" по следующим причинам

Во-первых, в этом сценарии закрытие неизбежно. Вы использовали часть локальных данных в анонимном методе, и поэтому для захвата состояния необходимо закрытие. Каждый вызов этого метода должен создавать новое закрытие по ряду причин. Поэтому каждый делегат будет указывать на метод экземпляра в этом замыкании.

Под капотом анонимный метод / выражение представлен производным экземпляром System.MulticastDelegate в коде. Если вы посмотрите на метод Equals этого класса, вы заметите 2 важные детали

  • Он запечатан, поэтому у производного делегата нет возможности изменить поведение равных
  • Часть метода Equals выполняет сравнительное сравнение объектов

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

3 голосов
/ 14 сентября 2009

РЕДАКТИРОВАТЬ: старый ответ оставлен для исторического значения ниже линии ...

CLR должен был бы отработать случаи, когда скрытые классы можно было бы считать равными, принимая во внимание все, что можно было бы сделать с захваченными переменными.

В этом конкретном случае захваченная переменная (x) не изменяется ни внутри делегата, ни в контексте захвата - но я бы предпочел, чтобы язык не требовал такого рода сложности анализа. Чем сложнее язык, тем сложнее его понять. Нужно было бы различить этот случай и тот, который приведен ниже, где значение захваченной переменной изменяется при каждом вызове - там есть большая разница, какой делегат вы вызываете; они ни в коем случае не равны.

Я думаю, что вполне разумно, что эта и без того сложная ситуация (закрытие часто понимается неправильно) не пытается быть слишком "умной" и вырабатывает потенциальное равенство.

IMO, вы должны определенно выбрать другой маршрут. Это концептуально независимые экземпляры Action. Фальсификация этого путем принуждения делегатов к целям - ужасный взлом IMO.


Проблема в том, что вы захватываете значение x в сгенерированном классе. Две переменные x являются независимыми, поэтому они являются неравными делегатами. Вот пример, демонстрирующий независимость:

using System;

class Test
{
    static void Main(string[] args)
    {
        Action first = CreateDelegate(1);
        Action second = CreateDelegate(1);
        first();
        first();
        first();
        first();
        second();
        second();
    }

    private static Action CreateDelegate(int x)
    {
        return delegate 
        { 
            Console.WriteLine(x);
            x++;
        };
    }
}

Выход:

1
2
3
4
1
2

РЕДАКТИРОВАТЬ: Чтобы посмотреть на это по-другому, ваша оригинальная программа была эквивалентна:

using System;

class Test
{
    static void Main(string[] args)
    {
        bool same = CreateDelegate(1) == CreateDelegate(1);
    }

    private static Action CreateDelegate(int x)
    {
        return new NestedClass(x).ActionMethod;
    }

    private class Nested
    {
        private int x;

        internal Nested(int x)
        {
            this.x = x;
        }

        internal ActionMethod()
        {
            int z = x;
        }
    }
}

Как вы можете сказать, будут созданы два отдельных экземпляра Nested, и они будут целями для двух делегатов. Они неравны, поэтому и делегаты неравны.

0 голосов
/ 15 сентября 2009

Такое поведение имеет смысл, потому что в противном случае анонимные методы могли бы быть перепутаны (если бы они имели одно и то же имя, учитывая одно и то же тело).

Вы можете изменить свой код на это:

static void Main(){   
    bool same = CreateDelegate(1) == CreateDelegate(1);
}

static Action<int> action = (x) => { int z = x; };

private static Action<int> CreateDelegate(int x){
    return action;
}

Или, желательно, так как это плохой способ его использования (плюс вы сравнивали результат, и у Action нет возвращаемого значения ... используйте Func <...>, если вы хотите вернуть значение) :

static void Main(){
    var action1 = action;
    var action2 = action;
    bool same = action1 == action2;  // TRUE, of course
}

static Action<int> action = (x) => { int z = x; };
0 голосов
/ 14 сентября 2009

Я не могу вспомнить ситуацию, в которой мне когда-либо приходилось это делать. Если мне нужно сравнить делегатов, я всегда использую именованные делегаты, иначе было бы возможно что-то подобное:

MyObject.MyEvent += delegate { return x + y; };

MyObject.MyEvent -= delegate { return x + y; };

Этот пример не подходит для демонстрации проблемы, но я мог бы предположить, что может возникнуть ситуация, когда разрешение этого может нарушить существующий код, который был разработан с ожиданием того, что это недопустимо.

Я уверен, что есть внутренние детали реализации, которые также делают это плохой идеей, но я не знаю точно, как анонимные методы реализованы внутри.

...