#c_sharp #clr
У всех классов, экземпляры которых должны/могут быть сохранены через BinaryFormater, обязан быть этот самый атрибут [Serializable]. Зачем он нужен? Нет, конечно, ясно, что он говорит среде CLR, мол, тип есть сериализуемый, поэтому, будь добра (среда) и сериализируй! В моем представлении, почти на пальцах, этот атрибут помогает MSV дописать и реализовать GetObjectData(SerializationInfo info, StreamingContext context) из интерфейса ISerializable для каждого поля в классе. Например, так: public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", name); info.AddValue("BDate", BDate); info.AddValue("Price", Price); } И реализовать конструктор для десериализации, вот так: public Engineer(SerializationInfo info, StreamingContext context) : base(info.GetString("Name"), info.GetDateTime("BDate")) { Price = info.GetDecimal("Price"); } НО, даже если и интерфейс ISerializable ты реализовал, то атрибут все равно нужен. CA2237: MarkISerializableTypesWithSerializable Что же он все таки делает, так сказать, под капотом?
Ответы
Ответ 1
В дополнение к ответу @Alexander Petrov, процитирую MSDN (в собственном переводе): Применяйте атрибут SerializableAttribute к типу для того, чтобы указать, что экземпляры этого типа разрешено сериализовывать. Если хотя бы один тип в графе сериализуемых объектов (то есть, тип корневого объекта или любого объекта, на который он внутри ссылается прямо или косвенно) не имеет этого атрибута, CLR выбросит SerializationException. Это предотвращает ошибки, связанные с непредусмотренной сериализацией типов, которые для сериализации вовсе не предназначены. Почему такое необходимо для бинарной сериализации? Дело в том, что бинарная сериализация нарушает инкапсуляцию и работает напрямую с закрытыми полями. При этом, в отличие от вызова сеттера для публичного свойства, легко может быть нарушен инвариант типа. Например, если каждый экземпляр класса получает при создании уникальный номер, то бинарная десериализация склонирует этот самый номер, нарушив тем самым логику программы. Чтобы избежать таких проблем, для участия типа в сериализации CLR требует осознанного решения программиста о том, что каждый конкретный может быть безопасно сериализирован. (Информация частично взята из этого ответа.)Ответ 2
Поискав объяснения и как следует подумав, я окончательно укрепился в мнении, которое давно подозревал: этот атрибут служит намёком не столько BinaryFormatterу, сколько человеку. Не сериализуй что попало! Например, при разработке на Windows Forms (когда вся логика в баттон-кликах, а данные - в полях форм) у новичков часто возникает соблазн сохранять внешний вид и вообще все данные программы с помощью сериализации главной формы. А что, просто же! Однако, если бы это было реализовано, то это привело бы к сохранению сотен, а то и тысяч, по большей части пустых (неизменённых с дефолтного значения) свойств форм со всеми вложенными контролами (в том числе изображения). Что, конечно же, избыточно и ненужно. Соответственнно, когда разработчик пишет класс, содержащий поля/свойства с данными, он должен определить, можно ли (нужно ли) его свободно (де)сериализовать и пометить при необходимости этим атрибутом. Другой разработчик, использующий этот класс, по наличию/отсутствию атрибута поймёт, что с ним можно делать. При необходимости ему придётся создать класс, специально предназначенный для хранения отдельных (и только нужных!) данных.Ответ 3
Добавлю еще немного информации к размышлению. Класс может быть сериализуемым и при этом не реализовывать интерфейс ISerializable [Serializable] class A { public int Value; } В этом случае будет применена сериализация по-умолчанию, т.е. будут сериализованы публичные поля и свойства. Но нужно учесть, что для последующей десериализации у класса должен быть конструктор по-умолчанию (без параметров), иначе вылетит исключение. В особых случаях требуется и атрибут, и реализация интерфейса. Опустим случай, когда разработчику нужно контролировать процесс исходя из соображений логики класса и возьмем пример где это необходимо независимо от желания разработчика. [Serializable] class B { public B Pred; public B Next; } По-умолчанию, если при сериализации встречается публичное поле содержащее объект, этот объект также сериализуется. К чему это приводит: в примере класса выше, это приведет к бесконечной рекурсии и выбросу StackOverflowException если существует два и более связанных объектов класса B т.к. каждый объект имеет ссылку на соседа, а тот, в свою очередь, имеет обратную ссылку. В данном случае этот класс обязан реализовать интерфейс ISerializable, не смотря на внешнюю простоту и отсутствие указаний со стороны компилятора. Ок, двусвязный список видно сразу. Возьмем более сложный пример: [Serializable] class A { public B Item; } [Serializable] class B { public C Item; } [Serializable] class C { public A Item; } Сериализация любого из этих объектов может привести к выбросу StackOverflowException если объекты, хранящиеся в свойствах Item образуют замкнутую цепочку. Не смотря на простоту примера, аналогичные конструкции встречаются довольно часто и наличие циклического графа объектов может быть далеко не очевидным. Так как атрибуты не наследуются и требуется чтобы все задействованные при сериализации объекты имели тип помеченный атрибутом Serializable, вероятность случайно сериализовать объект, который является частью цикла и тем самым вызвать переполнение стека сведена к минимуму, за исключением случаев синтетических примеров, как приведены выше, и явной ошибки(диверсии?) программиста пометившего атрибутом Serializable все что попалось под руку.Ответ 4
Двунаправленные списки, в том числе круговые, прекрасно сериализуются. Вот класс, который приведен как тот, что выбрасывает StackOverFlowException, сериализуем! Без пользовательской сериализации и других ухищрений. Как, думаете, сериализуются коллекции? А они все Serializable! Сейчас написал класс вот такого типа: [Serializable] public sealed class MyObjectCircle { public int Data; public MyObjectCircle Next = null; public MyObjectCircle Prev = null; } Конечно, заполнил его данными, примерно так: var obj = new MyObjectCircle(data1); var objNext = new MyObjectCircle(data2, null, obj); var objPrev = new MyObjectCircle(data3, obj); obj.Next = objNext; obj.Prev = objPrev; И что, он не сериализуется? Сериализуется, Бинарный форматтер, как надо. Десериализуется тоже без проблем. private void BtSerialize_Click ( object sender, EventArgs e ) { int.TryParse ( textBox6.Text, out var data1 ); int.TryParse(textBox7.Text, out var data2); int.TryParse(textBox8.Text, out var data3); var obj = new MyObjectCircle(data1); var objNext = new MyObjectCircle(data2, null, obj); var objPrev = new MyObjectCircle(data3, obj); obj.Next = objNext; obj.Prev = objPrev; var formatter = new BinaryFormatter ( ); Stream stream = new FileStream ( "serializing.bin", FileMode.Create ); formatter.Serialize ( stream, obj ); stream.Close ( ); TBInfo.Text = @"Сериализовано в ""serializing.bin"""; } } Серьезно, зачем сериализатору сериализировать то, что уже сериализовано? Я надеюсь,я правильно понял комментарий rdorn.
Комментариев нет:
Отправить комментарий