Помещение комментария там с объяснением сделало бы его читаемым и быстрым.
Это действительно зависит от типа проекта, и как важная производительность. Если Вы создаете 3D игру, то обычно существует большая общая оптимизация, которую Вы захотите бросить там по пути, и нет никакой причины не к (просто не увлекаются слишком рано). Но если Вы сделаете что-то хитрое, прокомментируете его так, то кто-либо смотрящий на него будет знать, как и почему Вы хитры.
РЕДАКТИРОВАТЬ: Старый ответ оставлен для исторической ценности под линией ...
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
, и они будут целями для двух делегатов. Они неравны, значит и делегаты неравны.
Я не знаю конкретных деталей этой проблемы в C #, но я работал над эквивалентной функцией VB.Net, которая имеет такое же поведение.
Суть в том, что такое поведение является «намеренным» по следующим причинам.
Во-первых, в этом сценарии закрытие неизбежно. Вы использовали часть локальных данных в анонимном методе, и, следовательно, необходимо закрытие для захвата состояния. Каждый вызов этого метода должен создавать новое закрытие по ряду причин. Поэтому каждый делегат будет указывать на метод экземпляра в этом закрытии.
Под капотом анонимный метод / выражение представлен производным экземпляром System.MulticastDelegate
в коде. Если вы посмотрите на метод Equals этого класса, вы заметите две важные детали
Это делает невозможным сравнение двух лямбда-выражений, прикрепленных к разным замыканиям, на равных.
Я не склонен думать, что это «ошибка». Более того, похоже, что вы предполагаете какое-то поведение в CLR, которого просто не существует.
Здесь важно понимать, что вы возвращаете новый анонимный метод (и инициализируете новый класс закрытия) каждый раз, когда вызываете Метод CreateDelegate
. Похоже, вы тестируете ключевое слово delegate
, чтобы использовать какой-то пул для внутренних анонимных методов. CLR определенно не делает этого. Делегат анонимного метода (как в случае лямбда-выражения) создается в памяти каждый раз, когда вы вызываете метод, и, поскольку оператор равенства, конечно, сравнивает ссылки в этой ситуации, ожидаемый результат return false
.
Хотя предложенное вами поведение может иметь некоторые преимущества в определенных контекстах, его, вероятно, будет довольно сложно реализовать и, скорее всего, это приведет к непредсказуемым сценариям. Я думаю, что текущее поведение генерации нового анонимного метода и делегата для каждого вызова является правильным, и я подозреваю, что это обратная связь, которую вы также получите в Microsoft Connect.
Если вы очень настойчивы в своем поведении описанный в вашем вопросе, всегда есть вариант мемоизации вашей функции CreateDelegate
, которая гарантирует, что один и тот же делегат будет возвращаться каждый раз для одних и тех же параметров. В самом деле, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которым Microsoft не рассматривала возможность реализации этого в CLR.
Если вы весьма настойчивы в том, чтобы иметь такое же поведение, как и вы. описанный в вашем вопросе, всегда есть вариант мемоизации вашей функции CreateDelegate
, которая гарантирует, что один и тот же делегат будет возвращаться каждый раз для одних и тех же параметров. В самом деле, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которым Microsoft не рассмотрела возможность реализации этого в CLR.
Если вы очень настойчивы в своем поведении описанный в вашем вопросе, всегда есть вариант мемоизации вашей функции CreateDelegate
, которая гарантирует, что один и тот же делегат будет возвращаться каждый раз для одних и тех же параметров. В самом деле, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которым Microsoft не рассматривала возможность реализации этого в CLR.
Если вы весьма настойчивы в том, чтобы иметь такое же поведение, как и вы. описанный в вашем вопросе, всегда есть возможность мемоизации вашей функции CreateDelegate
, которая гарантирует, что один и тот же делегат будет возвращаться каждый раз для одних и тех же параметров. В самом деле, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которым Microsoft не рассматривала возможность реализации этого в CLR.
Если вы весьма настойчивы в том, чтобы иметь такое же поведение, как и вы. описанный в вашем вопросе, всегда есть вариант мемоизации вашей функции CreateDelegate
, которая гарантирует, что один и тот же делегат будет возвращаться каждый раз для одних и тех же параметров. В самом деле, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которым Microsoft не рассмотрела возможность реализации этого в CLR.
Если вы очень настаиваете на поведении, описанном в вашем вопросе, всегда есть возможность запоминать вашу функцию CreateDelegate
, что обеспечит возврат того же делегата. каждый раз по одним и тем же параметрам. В самом деле, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которым Microsoft не рассмотрела возможность реализации этого в CLR.
Если вы очень настаиваете на поведении, описанном в вашем вопросе, всегда есть возможность запоминать вашу функцию CreateDelegate
, что обеспечит возврат того же делегата. каждый раз по одним и тем же параметрам. В самом деле, поскольку это так легко реализовать, это, вероятно, одна из нескольких причин, по которым Microsoft не рассмотрела возможность реализации этого в CLR.
Я не могу представить себе ситуацию, когда мне когда-либо приходилось бы это делать. Если мне нужно сравнить делегаты, я всегда использую именованные делегаты, иначе было бы возможно что-то вроде этого:
MyObject.MyEvent += delegate { return x + y; };
MyObject.MyEvent -= delegate { return x + y; };
Этот пример не очень хорош для демонстрации проблемы, но я предполагаю, что может возникнуть ситуация, когда разрешение этого может нарушить существующие код, который был разработан с ожиданием, что это недопустимо.
Я уверен, что есть детали внутренней реализации, которые также делают это плохой идеей, но я не знаю точно, как анонимные методы реализуются внутри.
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; };