Несмотря на то, что использовать механизм сериализации в C # довольно просто и удобно, есть некоторые моменты, на которые стоит обратить внимание. В этой статье рассказывается о том, как можно выстрелить себе в ногу, работая с сериализацией, о примерах кода, где есть основные подводные камни, а также о том, как PVS-Studio может помочь вам избежать неприятностей.
Для кого эта статья?
Эта статья будет особенно полезна тем, кто только начинает знакомиться с механизмом сериализации. Более опытные программисты тоже могут узнать что-то интересное или просто убедиться, что даже профессионалы делают ошибки.
Однако предполагается, что читатель уже в некоторой степени знаком с механизмом сериализации.
Но при чем тут PVS-Studio? В выпуске 6.05 мы добавили 6 диагностических правил, которые обнаруживают подозрительный код, используя механизм сериализации. Эти средства диагностики ищут в основном проблемные области, связанные с атрибутом [Serializable] или реализацией интерфейса ISerializable.
Примечание.
Следует понимать, что описанные в статье операторы актуальны для некоторых сериализаторов, например - BinaryFormatter и SoapFormatter ; для других, написанных вручную сериализаторами, поведение может быть другим. Например, отсутствие атрибута [Serializable] для класса не может предотвратить сериализацию и десериализацию с помощью настраиваемого сериализатора.
Кстати, если вы работаете с сериализацией, советую скачать пробную версию анализатора и проверить свой код на наличие подозрительных фрагментов.
Реализуя ISerializable, не забываем о конструкторе сериализации
Реализация типа интерфейса ISerializable помогает контролировать сериализацию, выбирая, какие элементы должны быть сериализованы, какие из них - нет, какие значения должны быть записаны во время сериализации элементов и т. Д. .
Интерфейс ISerializable содержит объявление одного метода - GetObjectData,, который будет вызываться при сериализации объекта. Но вместе с этим методом у нас всегда должен быть реализован конструктор, который будет вызываться при десериализации объекта. Поскольку интерфейс не может обязывать вас реализовать конструктор в классе, эта задача переходит к программисту, который выполняет сериализацию сериализуемого типа. Конструктор сериализации имеет следующую сигнатуру:
Ctor(SerializationInfo, StreamingContext)
Без этого конструктора сериализация объекта будет успешной (при условии, что метод GetObjectData реализован правильно), но восстановить (десериализовать) его будет невозможно - у нас будет исключение Исключение SerializationException.
Давайте посмотрим на пример такого кода из проекта Glimpse:
[Serializable] internal class SerializableTestObject : ISerializable { public string TestProperty { get; set; } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("TestProperty", this.TestProperty); } }
Предупреждение PVS-Studio: V3094 Возможное исключение при десериализации. Конструктор SerializableTestObject (SerializationInfo, StreamingContext) отсутствует. Glimpse.Test.AspNet SessionModelConverterShould.cs 111
Сериализация элемента этого класса будет успешной, но во время десериализации у нас будет исключение, потому что нет подходящего конструктора. Скорее всего, это не ошибка (судя по классу и имени файла), но как иллюстрация ситуации работает хорошо.
Конструктор сериализации для этого класса может выглядеть так:
protected SerializableTestObject(SerializationInfo info, StreamingContext context) { TestProperty = info.GetString(nameof(TestProperty)); }
Обратите внимание на модификатор доступа конструктора сериализации
При написании типа, реализующего интерфейс ISerializable, очень важно определить модификатор доступа для конструктора сериализации. Есть несколько возможных способов:
- конструктор сериализации объявляется с модификатором private в незапечатанном классе;
- конструктор сериализации объявляется с модификатором доступа public или internal;
- конструктор сериализации объявляется с модификатором protected в запечатанном классе.
Наибольший интерес для нас представляет первый вариант, так как он может быть самым опасным. Давайте кратко рассмотрим второй пункт, третий не так уж и полезен - компилятор не объявит член с модификатором protected в структуре (ошибка компиляции), если этот класс объявлен в запечатанный класс, компилятор выдаст предупреждение.
Конструктор сериализации в незапечатанном классе имеет модификатор доступа private.
Это наиболее опасная ситуация, когда модификаторы доступа неправильно применяются к конструкторам сериализации. Если тип незапечатанный, подразумевается, что он может иметь потомков. Однако, если конструктор сериализации имеет модификатор доступа private, он не может быть вызван из дочернего класса.
В этом случае у разработчика дочернего класса есть 2 варианта: либо вообще не использовать родительский класс, либо вручную десериализовать члены базового класса. Стоит отметить, что второй случай вряд ли можно считать решением проблемы:
- нет уверенности в том, что в базовом классе предусмотрена тривиальная десериализация членов;
- разработчик дочернего класса может забыть десериализовать член базового класса;
- Несмотря на желание сделать это, десериализовать частные члены базового класса будет невозможно.
Поэтому при написании незапечатанного сериализуемого класса обратите внимание на модификатор доступа, который имеет конструктор сериализации.
В ходе анализа мы обнаружили несколько проектов, в которых это правило не соблюдалось.
NHibernate
[Serializable] public class ConnectionManager : ISerializable, IDeserializationCallback { .... private ConnectionManager(SerializationInfo info, StreamingContext context) { .... } .... }
Предупреждение PVS-Studio: V3103 закрытый конструктор Ctor (SerializationInfo, StreamingContext) в незапечатанном типе не будет доступен при десериализации производных типов. NHibernate ConnectionManager.cs 276
Рослин
[Serializable] private class TestDiagnostic : Diagnostic, ISerializable { .... private ConnectionManager(SerializationInfo info, StreamingContext context) { .... } .... }
Предупреждение PVS-Studio: V3103 закрытый конструктор Ctor (SerializationInfo, StreamingContext) в незапечатанном типе не будет доступен при десериализации производных типов. NHibernate ConnectionManager.cs 276
В обоих примерах, приведенных выше, разработчик должен установить модификатор доступа protected для конструктора сериализации, чтобы дочерние классы могли вызывать его во время десериализации.
Не объявляйте конструктор сериализации с модификаторами public или internal.
Это совет «хорошего стиля программирования». Объявление конструктора сериализации с модификатором public или internal не приведет к ошибке, но в этом нет смысла - этот конструктор не предназначен для используется извне, и нет никакой разницы для сериализатора, у которого модификатор доступа имеет конструктор.
При проверке проектов с открытым кодом мы видели несколько случаев, когда это правило не учитывалось.
MSBuild
[Serializable] private sealed class FileState : ISerializable { .... internal SystemState(SerializationInfo info, StreamingContext context) { .... } .... }
Предупреждение PVS-Studio: V3103 Для десериализации следует использовать конструктор Ctor (SerializationInfo, StreamingContext). Делать его внутренним не рекомендуется. Подумайте о том, чтобы сделать его приватным. Microsoft.Build.Tasks SystemState.cs 218
[Serializable] private sealed class FileState : ISerializable { .... internal FileState(SerializationInfo info, StreamingContext context) { .... } .... }
Предупреждение PVS-Studio: V3103 Для десериализации следует использовать конструктор Ctor (SerializationInfo, StreamingContext). Делать его внутренним не рекомендуется. Подумайте о том, чтобы сделать его приватным. Microsoft.Build.Tasks SystemState.cs 139
В обоих случаях для конструктора сериализации должен быть установлен модификатор доступа private, поскольку оба класса запечатаны.
NHibernate
[Serializable] public class StatefulPersistenceContext : IPersistenceContext, ISerializable, IDeserializationCallback { .... internal StatefulPersistenceContext(SerializationInfo info, StreamingContext context) { .... } .... }
Предупреждение PVS-Studio: V3103 Для десериализации следует использовать конструктор Ctor (SerializationInfo, StreamingContext). Делать его внутренним не рекомендуется. Подумайте о том, чтобы сделать его защищенным. NHibernate StatefulPersistenceContext.cs 1478
[Serializable] public class Configuration : ISerializable { .... public Configuration(SerializationInfo info, StreamingContext context) { .... } .... }
Предупреждение PVS-Studio: V3103 Для десериализации следует использовать конструктор Ctor (SerializationInfo, StreamingContext). Делать это общедоступным не рекомендуется. Подумайте о том, чтобы сделать его защищенным. NHibernate Configuration.cs 84
Учитывая тот факт, что оба класса не запечатаны, мы должны установить protected в качестве модификатора доступа для конструкторов сериализации.
Реализуйте виртуальный метод GetObjectData в незапечатанных классах
Правило простое - когда вы пишете незапечатанный класс, реализуя интерфейс ISerializable, объявите метод GetObjectData с модификатором virtual. Это позволит дочерним классам выполнить правильную сериализацию объекта при использовании полиморфизма.
Чтобы нагляднее увидеть ситуацию, предлагаю взглянуть на несколько примеров.
Предположим, у нас есть следующие объявления родительского и дочернего классов.
[Serializable] class Base : ISerializable { .... public void GetObjectData(SerializationInfo info, StreamingContext context) { .... } } [Serializable] sealed class Derived : Base { .... public new void GetObjectData(SerializationInfo info, StreamingContext context) { .... } }
Предположим, у нас есть метод сериализации и десериализации объекта:
void Foo(BinaryFormatter bf, MemoryStream ms) { Base obj = new Derived(); bf.Serialize(ms, obj); ms.Seek(0, SeekOrigin.Begin); Derived derObj = (Derived)bf.Deserialize(ms); }
В этом случае сериализация будет выполнена некорректно, поскольку метод GetObjectData будет вызываться не для родительского, а для дочернего класса. Следовательно, члены дочернего класса не будут сериализованы. Если во время десериализации из объекта SerializationInfo мы получим значения членов, добавленные в метод GetObjectData дочернего класса, мы получим исключение, как объект Тип SerializationInfo не будет содержать обязательных ключей.
Чтобы исправить ошибку в родительском классе для метода GetObjectData, мы должны добавить модификатор virtual в производный класс - override.
Но если в родительском классе есть только явная реализация интерфейса ISerializable, вы не сможете добавить модификатор virtual. Однако, оставив все как есть, вы рискуете усложнить жизнь разработчикам дочерних классов.
Давайте посмотрим на пример реализации родительского и дочернего классов:
[Serializable] class Base : ISerializable { .... void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { .... } } [Serializable] sealed class Derived : Base, ISerializable { .... public void GetObjectData(SerializationInfo info, StreamingContext context) { .... } }
В этом случае мы не сможем получить доступ к методу GetObjectData родительского класса из дочернего класса. Кроме того, если у нас есть частные члены, сериализованные в базовом методе, невозможно будет получить к ним доступ из дочернего класса, а это означает, что у нас также не будет правильной сериализации. Чтобы исправить эту ошибку, мы должны добавить неявную реализацию в базовый класс виртуального метода GetObjectData, помимо явной реализации. Тогда исправленный код может выглядеть так:
[Serializable] class Base : ISerializable { .... void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { GetObjectData(info, context); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { .... } } [Serializable] sealed class Derived : Base { .... public override void GetObjectData(SerializationInfo info, StreamingContext context) { .... base.GetObjectData(info, context); } }
Или, если мы не собираемся наследовать этот класс, мы должны сделать его запечатанным, добавив модификатор sealed в объявление класса.
Рослин
[Serializable] private class TestDiagnostic : Diagnostic, ISerializable { private readonly string _kind; .... private readonly string _message; .... void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("id", _descriptor.Id); info.AddValue("kind", _kind); info.AddValue("message", _message); info.AddValue("location", _location, typeof(Location)); info.AddValue("severity", _severity, typeof(DiagnosticSeverity)); info.AddValue("defaultSeverity", _descriptor.DefaultSeverity, typeof(DiagnosticSeverity)); info.AddValue("arguments", _arguments, typeof(object[])); } .... }
Предупреждение PVS-Studio: V3104 реализация GetObjectData в незапечатанном типе TestDiagnostic не является виртуальной, возможна некорректная сериализация производного типа. CSharpCompilerSemanticTest DiagnosticAnalyzerTests.cs 112
TestDiagnostic не запечатан (хотя он является частным, поэтому от него может быть наследование в рамках одного и того же класса), но при этом он имеет только явную реализацию ISerializable interface , в котором сериализованы частные члены. Это означает следующее: разработчик дочернего класса не сможет сериализовать необходимые члены: метод GetObjectData недоступен, а модификатор доступа не разрешает доступ к членам напрямую.
Было бы лучше переместить весь приведенный выше код сериализации в виртуальный метод GetObjectData, и использовать его из явной реализации интерфейса.
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { GetObjectData(info, context); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("id", _descriptor.Id); info.AddValue("kind", _kind); info.AddValue("message", _message); info.AddValue("location", _location, typeof(Location)); info.AddValue("severity", _severity, typeof(DiagnosticSeverity)); info.AddValue("defaultSeverity", _descriptor.DefaultSeverity, typeof(DiagnosticSeverity)); info.AddValue("arguments", _arguments, typeof(object[])); }
Все сериализуемые члены должны иметь сериализуемый тип.
Это условие является обязательным для правильной сериализации объекта, независимо от того, является ли это автоматической сериализацией (когда тип аннотируется атрибутом [Serializable], и когда он не реализует ISerializable), либо сериализация выполняется вручную (реализовано ISerializable).
В противном случае, если во время сериализации у нас будет член, который не аннотирован атрибутом [Serializable], мы получим исключение типа SerializationException .
Если вы хотите сериализовать объект без членов, имеющих несериализуемый тип, существует несколько возможных вариантов:
- сделать несериализуемый тип сериализуемым;
- если есть автоматическая сериализация, аннотируйте поля, которые не предназначены для сериализации, с помощью атрибута [NonSerialized];
- если вы выполняете сериализацию вручную, просто игнорируйте те элементы, которые вам не нужны.
Обратите внимание на то, что атрибут [NonSerialized] можно применять только к полям. Таким образом, вы не сможете предотвратить сериализацию свойства, но, если оно имеет несериализуемый тип, вы получите исключение. Например, при попытке сериализовать SerializedClass определение приведено ниже:
sealed class NonSerializedType { } [Serializable] sealed class SerializedClass { private Int32 value; public NonSerializedType NSProp { get; set; } }
Мы обходим эту ситуацию, реализуя свойство через поле, аннотированное атрибутом [NonSerialized]:
[Serializable] sealed class SerializedClass { private Int32 value; [NonSerialized] private NonSerializedType nsField; public NonSerializedType NSProp { get { return nsField; } set { nsField = value; } } }
Диагностическое правило V3097 статического анализатора PVS-Studio способно обнаруживать такие ошибки, как сериализуемый тип, имеющий члены несериализуемых типов, не аннотированные атрибутом [NonSerialized].
Но опять же, я должен упомянуть, что это предупреждение не всегда обнаруживает настоящую ошибку - все будет зависеть от используемого сериализатора.
Давайте посмотрим на фрагменты кода, где это условие было нарушено.
Подтекст
public class BlogUrlHelper { .... } [Serializable] public class AkismetSpamService : ICommentSpamService { .... readonly BlogUrlHelper _urlHelper; .... }
Предупреждение PVS-Studio: V3097 Возможное исключение: тип AkismetSpamService, отмеченный [Serializable], содержит несериализуемые элементы, не отмеченные [NonSerialized]. Subtext.Framework AkismetSpamService.cs 31
Тип BlogUrlHelper поля _urlHelper не сериализуем, поэтому, если вы попытаетесь сериализовать экземпляр AkismetSpamService с помощью некоторых сериализаторов, мы получить исключение типа SerializationException. Мы должны решать проблему исходя из ситуации. Если вы используете сериализаторы типа BinaryFormatter или SoapFormatter - необходимо аннотировать поле атрибутом [NonSerialized] или аннотировать BlogUrlHepler тип с атрибутом [Serializable] . Если вы используете другие сериализаторы, для которых не требуется атрибут [Serializable] в сериализуемых полях, это намного проще.
NHibernate
public class Organisation { .... } [Serializable] public class ResponsibleLegalPerson { .... private Organisation organisation; .... }
Предупреждение PVS-Studio: V3097 Возможное исключение: тип ResponsibleLegalPerson, отмеченный [Serializable], содержит несериализуемые элементы, не отмеченные [NonSerialized]. NHibernate.Test ResponsibleLegalPerson.cs 9
Ситуация такая же, как и выше - все или ничего. Все зависит от сериализатора.
Не забывайте атрибут [Serializable] при реализации интерфейса ISerializable.
Этот совет относится к тем, кто только начинает работать с сериализацией. Управляя сериализацией вручную с помощью интерфейса ISerializable, легко забыть аннотировать тип с помощью [Serializable],, что потенциально может привести к исключению Тип SerializationException. Такой атрибут требуется для сериализаторов, таких как BinaryFormatter.
SharpDevelop
Интересный пример этой ошибки в проекте SharpDevelop.
public class SearchPatternException : Exception, ISerializable { .... protected SearchPatternException(SerializationInfo info, StreamingContext context) : base(info, context) { } }
Предупреждение PVS-Studio: V3096 Возможное исключение при сериализации типа SearchPatternException. Атрибут [Serializable] отсутствует. ICSharpCode.AvalonEdit ISearchStrategy.cs 80
public class DecompilerException : Exception, ISerializable { .... protected DecompilerException(SerializationInfo info, StreamingContext context) : base(info, context) { } }
Предупреждение PVS-Studio: V3096 Возможное исключение при сериализации типа DecompilerException. Атрибут [Serializable] отсутствует. ICSharpCode.Decompiler DecompilerException.cs 28
Чтобы передать объект исключения между доменами приложения, у нас есть его сериализация и десериализация. Соответственно, типы исключений должны быть сериализуемыми. В приведенных выше примерах типы SearchPatternException и DecompilerException наследуются от Exception и реализуют конструкторы сериализации, но в то же время не аннотируются. атрибутом [Serializable], что означает, что при попытке сериализации объектов этих типов (например, для передачи между доменами) у нас будет исключение SerializationException Типа сгенерирована. Таким образом, например, создав исключение в другом домене приложения, вы перехватите не выброшенное исключение, а SerializationException.
Убедитесь, что в GetObjectData все необходимые члены типа сериализованы.
Реализуя интерфейс ISerializable и определяя метод GetObjectData, вы берете на себя ответственность за члены типа, который будет сериализован, и значения, которые будут там записаны. В этом случае разработчикам предлагается большой простор в управлении сериализацией: в качестве сериализуемого значения, связанного с членом (честно говоря - с любой строкой), вы можете записать фактическое значение сериализованного объекта, результат работы какой-нибудь метод, константа или буквальное значение - все, что угодно.
Однако в этом случае большая ответственность ложится на плечи разработчика, потому что он должен помнить все члены, которые предназначены для сериализации, даже если они находятся в базовом классе. Мы все просто люди, поэтому иногда некоторые члены остаются забытыми.
Для обнаружения таких ситуаций в анализаторе PVS-Studio есть специальное правило V3099. Предлагаю посмотреть несколько примеров кода, которые были обнаружены этим правилом.
SharpDevelop
[Serializable] public abstract class XshdElement { public int LineNumber { get; set; } public int ColumnNumber { get; set; } public abstract object AcceptVisitor(IXshdVisitor visitor); } [Serializable] public class XshdColor : XshdElement, ISerializable { .... public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException("info"); info.AddValue("Name", this.Name); info.AddValue("Foreground", this.Foreground); info.AddValue("Background", this.Background); info.AddValue("HasUnderline", this.Underline.HasValue); if (this.Underline.HasValue) info.AddValue("Underline", this.Underline.Value); info.AddValue("HasWeight", this.FontWeight.HasValue); if (this.FontWeight.HasValue) info.AddValue("Weight", this.FontWeight .Value .ToOpenTypeWeight()); info.AddValue("HasStyle", this.FontStyle.HasValue); if (this.FontStyle.HasValue) info.AddValue("Style", this.FontStyle.Value.ToString()); info.AddValue("ExampleText", this.ExampleText); } }
Предупреждение PVS-Studio: V3099 Не все члены типа XshdColor сериализованы внутри метода GetObjectData: LineNumber, ColumnNumber. ICSharpCode.AvalonEdit XshdColor.cs 101
В этом коде нет описанных выше проблем, таких как неправильные модификаторы доступа в конструкторе сериализации или отсутствие атрибута [Serializable] или модификатора virtual для GetObjectData метод.
Увы, здесь все же ошибка. В методе GetObjectData свойства базового класса не учитываются, что означает, что некоторые данные будут потеряны во время сериализации. В результате при десериализации объект будет восстановлен с другим состоянием.
В этом случае решение состоит в том, чтобы вручную добавить необходимые значения, например, следующим образом:
info.AddValue(nameof(LineNumber), LineNumber); info.AddValue(nameof(ColumnNumber), ColumnNumber);
Если бы базовый класс также реализовал интерфейс ISerializable, решение было бы более элегантным - вызов в производном методе GetObjectData базового.
NHibernate
[Serializable] public sealed class SessionImpl : AbstractSessionImpl, IEventSource, ISerializable, IDeserializationCallback { .... void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { log.Debug("writting session to serializer"); if (!connectionManager.IsReadyForSerialization) { throw new InvalidOperationException("Cannot serialize a Session while connected"); } info.AddValue("factory", Factory, typeof(SessionFactoryImpl)); info.AddValue("persistenceContext", persistenceContext, typeof(StatefulPersistenceContext)); info.AddValue("actionQueue", actionQueue, typeof(ActionQueue)); info.AddValue("timestamp", timestamp); info.AddValue("flushMode", flushMode); info.AddValue("cacheMode", cacheMode); info.AddValue("interceptor", interceptor, typeof(IInterceptor)); info.AddValue("enabledFilters", enabledFilters, typeof(IDictionary<string, IFilter>)); info.AddValue("enabledFilterNames", enabledFilterNames, typeof(List<string>)); info.AddValue("connectionManager", connectionManager, typeof(ConnectionManager)); } .... private string fetchProfile; .... }
Предупреждение PVS-Studio: V3099 Не все члены типа SessionImpl сериализованы внутри метода GetObjectData: fetchProfile. NHibernate SessionImpl.cs 141
На этот раз поле текущего класса (fetchProfile) забыли сериализовать. Как видно из объявления, оно не аннотируется атрибутом [NonSerialized] (в отличие от других полей, которые не сериализуемы в методе GetObjectData).
В проекте было еще два подобных фрагмента:
- V3099 Не все элементы типа Configuration сериализованы внутри метода GetObjectData: currentDocumentName, preMappingBuildProcessed. NHibernate Configuration.cs 127
- V3099 Не все члены типа ConnectionManager сериализованы внутри метода GetObjectData: flushingFromDtcTransaction. NHibernate ConnectionManager.cs 290
В подобного рода ошибках есть довольно интересная вещь - они либо приводят к выбросу исключения, либо к логическим ошибкам, которые действительно трудно обнаружить.
Исключение будет выдано в том случае, если в конструкторе сериализации программист пытается получить значение только что добавленного поля (и доступ по отсутствующему ключу). Если член был полностью забыт (как в GetObjectData, так и в конструкторе сериализации), то состояние объекта будет повреждено.
Резюме
Кратко обобщая всю информацию, можно сформулировать несколько советов и правил:
- Аннотируйте типы, реализуя интерфейс ISerializable с атрибутом [Serializable].
- Убедитесь, что все члены, аннотированные атрибутом [Serializable], правильно сериализованы;
- Реализуя интерфейс ISerializable, не забудьте реализовать конструктор сериализации (Ctor (SerializationInfo, StreamingContext));
- В запечатанных типах установите модификатор доступа private для конструктора сериализации, в незапечатанных - protected;
- В незапечатанных типах, реализующих интерфейс ISerializable, сделайте метод GetObjectData виртуальным;
- Убедитесь, что в GetObjectData все необходимые члены сериализованы, включая члены базового класса, если они есть.
Вывод
Надеюсь, вы узнали что-то новое из этой статьи и стали экспертом в области сериализации. Придерживаясь правил и следуя советам, которые мы дали выше, вы сэкономите время на отладке программы и упростите жизнь себе и другим разработчикам, работающим с вашими классами. Также вам поможет анализатор PVS-Studio, позволяющий обнаруживать такие ошибки сразу после их появления в вашем коде.
Дополнительная информация
- V3094. Возможное исключение при десериализации типа. Конструктор Ctor (SerializationInfo, StreamingContext) отсутствует
- V3096. Возможное исключение при сериализации типа. Атрибут [Serializable] отсутствует
- V3097. Возможное исключение: тип, отмеченный [Serializable], содержит несериализуемые элементы, не отмеченные [NonSerialized].
- V3099. Не все члены типа сериализуются внутри метода GetObjectData.
- V3103. Конструктор частного Ctor (SerializationInfo, StreamingContext) в незапечатанном типе будет недоступен при десериализации производных типов.
- V3104. Реализация GetObjectData в незапечатанном типе не виртуальная, возможна некорректная сериализация производного типа.
- MSDN. Сериализация в .NET Framework
- MSDN. Пользовательская сериализация