Ограничение переменной С#

A (еще??) вопрос о том, как применяется переменная область применительно к закрытию. Вот минимальный пример:

public class Foo
{
    public string name;
    public Foo(string name)
    {
        this.name = name;
    }
}


public class Program
{
    static Action getAction(Foo obj)
    {
        return () => Console.WriteLine(obj.name); 
    }

    static void Main(string[] args)
    {
        Foo obj1 = new Foo("x1");    
        Action a = getAction(obj1);  
        obj1 = new Foo("x2");        
        a();                         
    }
}

Отпечатает x1. Это можно объяснить следующим образом:

getAction возвращает анонимную функцию с закрытием, включающим переменную obj. obj имеет то же значение ссылки, что и obj1, но его связь с obj1 заканчивается там, так как замыкание охватывает только obj. Другими словами, любое значение obj1 после этого не влияет на закрытие. Поэтому всякий раз, когда вы вызываете (однако a) (например, a передается какой-либо другой функции), он всегда печатает x1.

Теперь мои вопросы:

  • Правильно ли указано это выше?
  • У меня нет конкретного сценария, но что, если мы хотим, чтобы программа печатала x2 (например, закрытие для внешней оболочки)? Может ли это быть сделано (или не имеет смысла даже пытаться)?

Ответ 1

Рассмотрим:

static Action getAction(Foo obj)
{
    return () => Console.WriteLine(obj.name); 
}

Замыкание выполняется над параметром obj; этот obj является ссылкой, переданной значением, поэтому, если вызывающий абонент:

x = someA();
var action = getAction(x);
x = someB(); // not seen by action

то закрытие по-прежнему превышает исходное значение, потому что ссылка (а не объект) копируется при передаче ее на getAction.

Обратите внимание, что если вызывающий абонент меняет значения на исходном объекте, это будет видно по методу:

x = someA();
var action = getAction(x);
x.name = "something else"; // seen by action

Внутри метода getAction это в основном:

var tmp = new SomeCompilerGeneratedType();
tmp.obj = obj;
return new Action(tmp.SomeCompilerGeneratedMethod);

с:

class SomeCompilerGeneratedType {
    public Foo obj;
    public void SomeCompilerGeneratedMethod() {
        Console.WriteLine(obj.name); 
    }
}

Ответ 2

Короткий ответ: объяснение правильное и если вы хотите изменить значение от x1 до x2, тогда вам нужно изменить конкретный объект, который передается действию.

obj1.name = 'x2'

Когда объект передается функции как параметр, он копирует ссылку (указатель) в объект.

В то время у вас есть один объект и две ссылки;

  • Foo obj1, который является переменной в Main и
  • Foo obj, который является переменной в getAction

Всякий раз, когда вы решите установить другой объект (или null) на obj1, он не повлияет на вторую ссылку в getAction.

Ответ 3

Вот IL, сгенерированный для Main:

IL_0000:  ldstr       "x1"
IL_0005:  newobj      UserQuery+Foo..ctor
IL_000A:  stloc.0     // obj1
IL_000B:  ldloc.0     // obj1
IL_000C:  call        UserQuery.getAction
IL_0011:  stloc.1     // a
IL_0012:  ldstr       "x2"
IL_0017:  newobj      UserQuery+Foo..ctor
IL_001C:  stloc.0     // obj1
IL_001D:  ldloc.1     // a
IL_001E:  callvirt    System.Action.Invoke
IL_0023:  ret      

из которого я выводю, что при вызове getAction() он создает метод со значениями в нем для obj1, когда вы создаете новый экземпляр и вызываете делегата, из-за закрытия он имеет предыдущее значение в своем компиляторе созданный метод, благодаря которому он печатает x1

Когда вы вызываете getAction(obj1), Foo obj теперь ссылается на новый Foo ( "X1" ) ``, то вы делаете obj1 = new Foo("x2"), теперь obj1 имеет ссылку new Foo("x2"), но Foo obj of getAction(Foo obj) все еще ссылается на new Foo("x1")

obj1 = new Foo("x1")  // obj1 is referencing to ----> Foo("x1") memory location
getAction(obj1)  // --> getAction(Foo obj) obj is referencing to Foo("x1")
obj1 = new Foo("x2") //  obj1 is now referencing to----> Foo("x2") memory location
// but in getAction(Foo obj) obj is still referencing to Foo("x1")

Ответ 4

Вы можете переписать свой код на

public class Program
{
    static void Main(string[] args)
    {
        Foo obj1 = new Foo("x1");
        // rewrite of
        // Action a = GetAction( obj1 );
        Foo obj = obj1;
        Action a = () => Console.WriteLine( obj.name );  

        obj1 = new Foo("x2");        
        a();                         
    }
}

Это то, что происходит внутри страны. Вы назначаете ссылку на obj и создаете действие, относящееся к obj.

Ответ 5

Вы объясняете правильность и в основном способ перефразировать то, что написано в Спецификации языка С# в Раздел 5.1.4 (см. здесь для полнота, акцент мой):

Параметр, объявленный без модификатора ref или out, является значением параметр.

Параметр значения возникает при вызове член функции (метод, конструктор экземпляра, аксессор или оператор) (раздел 7.4), которому принадлежит параметр, а - инициализируется значением аргумента, указанного в вызове. A параметр значения перестает существовать при возврате члена функции.

Для определенной проверки присвоения значения параметр значения считается первоначально назначенным.