C# делегаты, эталонное время разрешения
У меня простой вопрос о делегатах.net. Скажем, у меня есть что-то вроде этого:
public void Invoke(Action<T> action)
{
Invoke(() => action(this.Value));
}
public void Invoke(Action action)
{
m_TaskQueue.Enqueue(action);
}
Первая функция содержит ссылку на this.Value
, Во время выполнения, когда вызывается первый метод с универсальным параметром, он обеспечивает this.Value
как-то на второй, но как? Это пришло мне в голову:
- Call by value (структура) - текущее значение
this.Value
проходит, так что еслиm_TaskQueue
выполняется через 5 минут, значение не будет в его недавнем состоянии, оно будет таким, каким оно было при первой ссылке. - Вызов по ссылке (тип ссылки) - тогда самое последнее состояние
Value
будут ссылаться во время выполнения действия, но если я изменюthis.Value
на другую ссылку перед выполнением действия, она все равно будет указывать на старую ссылку - Звоните по имени (оба) - где
this.Value
будет оцениваться, когда будет вызвано действие. Я считаю, что фактическая реализация будет содержать ссылку наthis
затем оценитеValue
на это во время фактического выполнения делегата, так как нет никакого вызова по имени.
Я предполагаю, что это будет стиль Call of name, но не смог найти ни одной документации, которая бы задавалась вопросом, является ли это четко определенным поведением. Этот класс похож на Actor в Scala или Erlang, поэтому мне нужно, чтобы он был безопасным для потоков. я не хочу Invoke
функция разыменования Value
немедленно, это будет сделано в безопасной ветке для this
возражать m_TaskQueue
,
3 ответа
Позвольте мне ответить на ваш вопрос, описав, какой код мы на самом деле генерируем для этого. Я переименую ваш смущенно названный другой метод Invoke; нет необходимости понимать, что здесь происходит.
Предположим, вы сказали
class C<T>
{
public T Value;
public void Invoke(Action<T> action)
{
Frob(() => action(this.Value));
}
public void Frob(Action action)
{ // whatever
}
}
Компилятор генерирует код, как будто вы на самом деле написали:
class C<T>
{
public T Value;
private class CLOSURE
{
public Action<T> ACTION;
public C<T> THIS;
public void METHOD()
{
this.ACTION(this.THIS.Value);
}
}
public void Invoke(Action<T> action)
{
CLOSURE closure = new CLOSURE();
closure.THIS = this;
closure.ACTION = action;
Frob(new Action(closure.METHOD));
}
public void Frob(Action action)
{ // whatever
}
}
Это отвечает на ваш вопрос?
Делегат хранит ссылку на переменную, а не ее значение. Если вы хотите сохранить текущее значение, то (при условии, что это тип значения) вам нужно сделать его локальную копию:
public void Invoke(Action<T> action)
{
var localValue = this.Value;
Invoke(() => action(localValue));
}
Если это изменяемый ссылочный тип, вы можете сделать локальный клон / глубокую копию.
Реальный ключ должен помнить, что область применения является лексической; это то, о чем заботится компилятор. Таким образом, он фиксирует переменные, а не их значения. Являются ли эти значения типами значений или ссылочными типами, совершенно другой вопрос.
Может быть, поможет немного более экстремальный пример изменения поведения делегата:
var myVariable = "something";
Action a = () => Console.WriteLine(myVariable);
myVariable = "something else entirely"
a();
печатает "что-то еще полностью". В этом свете не имеет значения, сколько раз вы оборачиваете, сохраняете или перемещаете функцию; он по-прежнему ссылается на вложенную переменную. Итак, вкратце, значение имеет значение вложенной переменной, когда делегат фактически выполняется.