2009-08-14 5 views
5

Скажем, у меня есть следующий код:Анонимные методы, область и сериализации

public class Foo 
{ 
    private int x; 
    private int y; 

    public Bar CreateBar() 
    { 
     return new Bar(x,() => y); 
    } 
} 

[Serializable] 
public class Bar 
{ 
    private int a; 
    private Func<int> b; 

    public Bar(int a, Func<int> b) 
    { 
     this.a = a; 
     this.b = b; 
    } 
} 

Что происходит с областью объектов и ценностей в этом случае? Поскольку x является типом значения, он передается в Bar по значению, и поэтому в его область ничего не должно произойти. Но что происходит с y? Значение y должно придерживаться, чтобы возвращаться, когда b фактически оценивается. Все ли Foo хранится вокруг, чтобы оценить y в более позднее время? Я могу только предположить, что Foo не GC.

Теперь предположим, что мы сериализуем Bar на диск, а затем десериализуем его позже. Что на самом деле было сериализовано? Это тоже сериал Foo? Какая магия продолжалась, чтобы b можно было оценить после десериализации Бар? Можете ли вы объяснить, что происходит в IL?

ответ

5

Update: чтобы увидеть, что на самом деле происходит без необходимости прибегать к IL: Using reflector to understand anonymous methods and captured variables


При использовании:

public Bar CreateBar() 
{ 
    return new Bar(x,() => y); 
} 

Вы неявно смысл this.y; поэтому с точки зрения делегата, это ссылка на Foo, которая включена. Таким образом, экземпляр Bar (через делегата) сохраняет все Foo живыми (не собранными с мусором) до тех пор, пока Bar не будет доступен для сбора.

В частности, нет необходимости (в данном случае) для компилятора генерировать дополнительный класс для обработки захваченных переменных; единственное, что требуется, это экземпляр Foo, поэтому способ может быть сгенерирован на Foo. Это было бы более сложным, если бы делегат включал локальные переменные (кроме this).

С точки зрения сериализации ... ну, во-первых, я бы сказал, что сериализация делегатов - очень плохая идея. Тем не менее, делегаты ходьбы BinaryFormatterбудет, и вы можете (в теории), в конечном итоге с сериализованном Bar, упорядоченный Foo и упорядоченный делегата связать их - но только если вы отмечаете Foo как [Serializable].

Но я подчеркиваю - это Плохая идея. Я редко использую BinaryFormatter (по целому ряду причин), но общий вопрос, который я вижу у людей, использующих его, - «почему он пытается сериализовать (некоторый случайный тип)». Обычно ответом является «вы публикуете событие, и оно пытается сериализовать подписчика», и в этом случае наиболее распространенным решением было бы пометить поле события как [NonSerialized].


Вместо того, чтобы смотреть на ИЛ; Другой способ исследовать это - использовать рефлектор в режиме .NET 1.0 (т. е. без его замены в анонимных методах); то вы можете увидеть:

public Bar CreateBar() 
{ 
    return new Bar(this.x, new Func<int>(this.<CreateBar>b__0)); 
} 
[CompilerGenerated] 
private int <CreateBar>b__0() 
{ 
    return this.y; 
} 

Как вы можете видеть; вещь, переданная в Bar, является делегатом скрытого метода (называемого <CreateBar>b__0()) текущего экземпляра (this). Таким образом, это является экземпляром к текущему Foo, который передается в Bar.

+0

В разделе 6.5.3 языка программирования C# (3-е издание) приведен пример, очень похожий на этот случай, и он обрабатывается так же, как Марк объясняется методом экземпляра экземпляра компилятора на Foo. –

+0

Фантастический ответ Марк! Благодаря! –

0

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

+0

Мне также интересно узнать об ИЛ и теории этого. Я написал тесты, чтобы ответить на некоторые из моих вопросов, но я хотел бы больше понять, что происходит на самом деле. Я думал, что некоторые супер умные люди на SO могут пролить немного света. –

+0

Вы можете использовать Reflector, чтобы увидеть IL, произведенный вашим тестовым проектом, но, конечно, комментарии экспертов будут более понятными. –

1

У меня возникла ошибка при попытке сериализации, когда она отражала объект для сериализации.

мой пример:

[Serializable] 
    public class SerializeTest 
    { 
     //public SerializeTest(int a, Func<int> b) 
     //{ 
     // this.a = a; 
     // this.b = b; 
     //} 

     public SerializeTest() 
     { 

     } 

     public int A 
     { 
      get 
      { 
       return a; 
      } 

      set 
      { 
       a = value; 
      } 
     } 
     public Func<int> B 
     { 
      get 
      { 
       return b; 
      } 
      set 
      { 
       b = value; 
      } 
     } 


     #region properties 

     private int a; 
     private Func<int> b; 



     #endregion 

     //serialize itself 
     public string Serialize() 
     { 
      MemoryStream memoryStream = new MemoryStream(); 

      XmlSerializer xs = new XmlSerializer(typeof(SerializeTest)); 
      using (StreamWriter xmlTextWriter = new StreamWriter(memoryStream)) 
      { 
       xs.Serialize(xmlTextWriter, this); 
       xmlTextWriter.Flush(); 
       //xmlTextWriter.Close(); 
       memoryStream = (MemoryStream)xmlTextWriter.BaseStream; 
       memoryStream.Seek(0, SeekOrigin.Begin); 
       StreamReader reader = new StreamReader(memoryStream); 

       return reader.ReadToEnd(); 
      } 
     } 

     //deserialize into itself 
     public void Deserialize(string xmlString) 
     { 
      String XmlizedString = null; 

      using (MemoryStream memoryStream = new MemoryStream()) 
      { 
       using (StreamWriter w = new StreamWriter(memoryStream)) 
       { 
        w.Write(xmlString); 
        w.Flush(); 

        XmlSerializer xs = new XmlSerializer(typeof(SerializeTest)); 
        memoryStream.Seek(0, SeekOrigin.Begin); 
        XmlReader reader = XmlReader.Create(memoryStream); 

        SerializeTest currentConfig = (SerializeTest)xs.Deserialize(reader); 

        this.a = currentConfig.a; 
        this.b = currentConfig.b; 

        w.Close(); 
       } 
      } 
     } 

    } 

class Program 
    { 
     static void Main(string[] args) 
     { 

      SerializeTest test = new SerializeTest() { A = 5, B =()=>67}; 
      string serializedString = test.Serialize(); 


} 
} 

Вот ссылка делать это ... немного сложнее: Serializing Anon Delegates

+0

Вы правы, в моем примере Foo также должен быть помечен как Serializable. Таким образом, это означает, что он сериализует все Foo. –

+0

@Stefan: no, ни один из ваших классов не нуждается в атрибуте Serializable (по крайней мере, не для сериализации XML). Этот атрибут предназначен для сериализации с форматировщиками (BinaryFormatter, SoapFormatter ...) –

+0

@Thomas Правильно, я использую BinaryFormatter. –

0

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

internal class CompilerGeneratedClassName 
{ 
    private int x; 
    private int y; 
    public CompilerGeneratedClassName(int x, int y) 
    { 
    this.x = x; 
    this.y = y; 
    } 

    public int CompilerGeneratedMethodName() 
    { 
    return this.y; 
    }  
} 

и

return new Bar(x,() => y); 

может быть заменен

return new Bar(x,new CompilerGeneratedClassName(x,y).CompilerGeneratedMethodName); 

Так что я не думаю, что там будет ссылка к объекту Foo в результате этого закрытия. Таким образом, объект Foo может быть GCed. Я могу ошибаться. Одна вещь, которую вы можете сделать, это написать небольшую программу, скомпилировать ее и проверить сгенерированный IL в инструменте ILDASM.

+0

Я проверил IL, но я не так хорошо знаком с IL и не могу понять ничего особенного, что сделал компилятор. –

+1

Для этого компилятор не нуждается в классе замыкания; это поле, а не локальная переменная метода. Это «это» передается. См. Мой ответ для большего. –