Подтвердить что ты не робот

Как BinaryFormatter.Deserialize создает новые объекты?

Когда BinaryFormatter десериализует поток в объекты, он создает новые объекты без вызова конструкторов.

Как это делается? И почему? В .NET это что-то еще?

Вот демо:

[Serializable]
public class Car
{
    public static int constructionCount = 0;

    public Car()
    {
        constructionCount++;
    }
}

public class Test
{
    public static void Main(string[] args)
    {
        // Construct a car
        Car car1 = new Car();

        // Serialize and then deserialize to create a second, identical car
        MemoryStream stream = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, car1);
        stream.Seek(0, SeekOrigin.Begin);
        Car car2 = (Car)formatter.Deserialize(stream);

        // Wait, what happened?
        Console.WriteLine("Cars constructed: " + Car.constructionCount);
        if (car2 != null && car2 != car1)
        {
            Console.WriteLine("But there are actually two.");
        }
    }
}

Вывод:

Cars constructed: 1
But there are actually two.

4b9b3361

Ответ 1

Есть две вещи, вызывающие конструктор (или, по крайней мере, должны делать).

Один из них заключается в том, чтобы выделить определенный объем памяти для объекта и сделать все домашнее хозяйство необходимым для того, чтобы он был объектом для остальной части мира .NET(обратите внимание на определенную величину ручной работы в этом объяснении).

Другим является помещение объекта в допустимое начальное состояние, возможно, на основе параметров - это то, что будет делать фактический код в конструкторе.

Дезициализация делает то же самое, что и первый шаг, вызывая FormatterServices.GetUninitializedObject, а затем делает то же самое, что и второй шаг, устанавливая значения полей равными тем, которые были записаны во время сериализации (что может потребовать десериализация других объектов, подлежащих указанным значениям).

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

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

Теперь это может быть проблемой в некоторых случаях - возможно, у нас есть поля readonly, которые могут быть заданы только конструктором, или, возможно, у нас есть побочные эффекты, которые мы хотим выполнить.

Решение обоих заключается в переопределении поведения сериализации с помощью ISerializable. Это будет сериализоваться на основе вызова ISerializable.GetObjectData, а затем вызовет конкретный конструктор с полями SerializationInfo и StreamingContext для десериализации (указанный конструктор может даже быть частным - это означает, что большинство других кодов даже не увидит его). Следовательно, если мы можем десериализовать поля readonly и иметь любые побочные эффекты, которые мы хотим (мы также можем делать все возможное, чтобы контролировать то, что сериализовано и как).

Если мы просто заботимся о том, чтобы какой-то побочный эффект произошел при десериализации, которая произошла при построении, мы можем реализовать IDeserializationCallback, и мы получим IDeserializationCallback.OnDeserialization, когда будет завершена десериализация.

Что касается других вещей, которые делают то же самое, что и в этом, в .NET существуют другие формы сериализации, но все, что я знаю. Можно вызвать FormatterServices.GetUninitializedObject самостоятельно, но запретить случай, когда у вас есть сильная гарантия того, что последующий код поместит объект в правильное состояние (то есть именно такую ​​ситуацию, в которой вы находитесь, при десериализации объекта из данных, созданных сериализацией такой же тип объекта), делая это чревато и хороший способ создать очень трудную диагностику ошибок.

Ответ 2

Дело в том, что BinaryFormatter на самом деле не делает ваш конкретный объект. Это вернет объект в память. График объектов - это в основном представление вашего объекта в памяти; это было создано, когда объект сериализован. Затем вызов deserialize в основном просто фиксирует этот график обратно в памяти как объект с открытым указателем, а затем он получает отличную от того, что на самом деле является кодом. Если он ошибается, то генерируется исключение.

Что касается вашего конкретного примера, вы действительно строите только один автомобиль; вы просто делаете точный дубликат этого автомобиля. Когда вы сериализуете его в потоке, вы храните точную двоичную копию. Когда вы десериализируете его, вам не нужно ничего строить. Он просто привязывает график в памяти к некоторому значению указателя как объекта и позволяет делать с ним все, что вам нужно.

Ваше сравнение car1!= car2 истинно из-за этого другого местоположения указателя, так как Car является ссылочным типом.

Почему? Честно говоря, легко просто потянуть двоичное представление, вместо того, чтобы идти и тянуть каждое свойство и все такое.

Я не уверен, использует ли что-нибудь еще в .NET эту же процедуру; наиболее вероятными кандидатами были бы все, что использует двоичный объект в некотором формате во время сериализации.