2010-02-16 2 views
7

У меня есть простой вопрос о .net делегатов. Скажем, у меня есть что-то вроде этого:C# делегатов, ссылка времени разрешения

public void Invoke(Action<T> action) 
    { 
     Invoke(() => action(this.Value)); 
    } 

    public void Invoke(Action action) 
    { 
     m_TaskQueue.Enqueue(action); 
    } 

Первая функция охватывающую ссылку на this.Value. Во время выполнения, когда вызывается первый метод с общим параметром, он будет предоставлять this.Value как-то ко второму, но как? Это пришло мне в голову:

  • Вызов по значению (структуры) - текущее значение this.Value получает прошло, так что если m_TaskQueue выполняет его через 5 минут, то значение не будет в своем недавнем состоянии, его будет тем, что было при первом обращении.
  • Вызов по ссылке (ссылочный тип) - то будет ссылаться самым последнее состояние Value во время выполнения действия, но если я изменю this.Value на другую ссылку перед выполнением действия, он по-прежнему будет указывать на старые ссылки
  • Вызов по имени (оба) - где this.Value будет оцениваться при вызове действия. Я считаю, что фактическая реализация будет содержать ссылку на , а затем оценить Value на то, что во время фактического выполнения делегата, поскольку нет вызова по имени.

Я предполагаю, что это был бы вызов стиля имени, но не мог найти никакой документации, поэтому задаваться вопросом, является ли это четко определенным поведением. Этот класс - это что-то вроде Актера в Скале или Эрланге, поэтому мне нужно, чтобы он был потокобезопасным. Мне не нужна функция Invoke для разыменования Value немедленно, что будет сделано в безопасной нити для объекта this по m_TaskQueue.

ответ

18

Позвольте мне ответить на ваш вопрос, описав, какой код мы фактически генерируем для этого. Я переименую ваш другой метод 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 
    } 
} 

, что ответить на ваш вопрос?

+0

Большое спасибо, это кристально ясно сейчас :) –

7

Делегат хранит ссылку на переменную, а не ее значение. Если вы хотите сохранить текущее значение, то (при условии, что тип значения), необходимо создать локальную копию:

public void Invoke(Action<T> action) 
{ 
    var localValue = this.Value; 
    Invoke(() => action(localValue)); 
} 

Если это изменяемый тип ссылки вы можете сделать местный клон/глубокая копия ,

+1

Обязательный Eric Lippert plug: http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx –

3

Настоящий ключ должен помнить, что область действия лексическая; это то, о чем компилятор заботится. Поэтому он фиксирует переменные, а не их значения. Являются ли эти значения типами значений или ссылочными типами другое дело.

Может быть немного более экстремальный пример изменения поведения делегата поможет:

var myVariable = "something"; 
Action a =() => Console.WriteLine(myVariable); 
myVariable = "something else entirely" 
a(); 

печатает «что-то совсем другое». В этом свете не имеет значения, сколько раз вы обертываете, сохраняете или перемещаете функцию; он по-прежнему относится к переменной, которую она вложила. Итак, короче говоря, имеет значение значение вложенной переменной, когда фактически делегирован делегат.

Смежные вопросы