Создание двух экземпляров делегата к тому же анонимному методу не равно

Помещение комментария там с объяснением сделало бы его читаемым и быстрым.

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

12
задан 14 September 2009 в 17:34
поделиться

5 ответов

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

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

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

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

ИМО, вам следует определенно выберут другой маршрут. Это концептуально независимые экземпляры 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 , и они будут целями для двух делегатов. Они неравны, значит и делегаты неравны.

5
ответ дан 2 December 2019 в 05:15
поделиться

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

Суть в том, что такое поведение является «намеренным» по следующим причинам.

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

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

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

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

4
ответ дан 2 December 2019 в 05:15
поделиться

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

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

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

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

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

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

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

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

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

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

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

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

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

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

19
ответ дан 2 December 2019 в 05:15
поделиться

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

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

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

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

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

1
ответ дан 2 December 2019 в 05:15
поделиться

This behaviour makes sense because otherwise anonymous methods would get mixed up (if they had the same name, given the same body).

You could change your code to this:

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;
}

Or, preferably, since that's a bad way to use it (plus you were comparing the result, and Action doesn't have a return value ... use Func<...> if you want to return a value):

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
ответ дан 2 December 2019 в 05:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: