docs/tools/illink/serialization.md
Trimming tools cannot analyze the patterns typically used by reflection-based serializers. Such serializers should be annotated with RequiresUnreferencedCodeAttribute, and using them in a trimmed app will likely not work (or will work unpredictably). Trimming tools will produce static analysis warnings for these patterns.
If possible, avoid using reflection-based serializers with trimming, and prefer solutions based on source generators where the serialized types and all required members are statically referenced.
As a last resort, trimming tools does have limited heuristics that can be enabled to keep some of the types and members required for serialization, but this provides no correctness guarantees; apps which use reflection-based serializers are still considered "broken" as far as the static analysis can tell, and it is up to you to make sure that the app works as intended.
Serialization discovery is disabled by default, and can be enabled by passing --enable-serialization-discovery.
Trimming tools have historically been used for Xamarin scenarios that use reflection-based serializers like XmlSerializer, since before the introduction of the trim analysis warnings. There were limited heuristics to satisfy some simple uses of serializers. To provide backwards compatibility for such scenarios, trimming tools have built-in heuristics that makes some simple cases "just work", albeit in an opaque and unpredictable way.
Consider disabling this behavior if possible, but it may be necessary when using legacy serializers that don't provide source generators or a similar solution that is statically analyzable. The following is a description of the heuristics for anyone who is unfortunate enough to have to rely on this behavior.
There are four parts to the heuristics:
The heuristics will keep detected serialized types only when it sees that the app has a call to a serializer constructor:
DataContractSerializer or DataContractJsonSerializer ctors will cause types attributed with DataContractSerializer attributes and their type graph to be preservedXmlSerializer ctor will cause types attributed with XmlSerializer attributes and their type graph to be preservedEven if the app contains attributed types for serialization, they will not be kept unless the serializer-specific construcrtor is called. Note that the preservation logic for a given serializer will be activated for all discovered types for that serializer, even if the constructor call doesn't actually serialize those types. For example:
new XmlSerializer (typeof (Foo));
var t = typeof (Bar);
class Foo
{
int removedField;
}
[XmlRoot]
class Bar
{
int keptField;
}
Here, the call to the XmlSerializer ctor activates the serialization logic, causing Bar to be considered a root even though it is not the type being serialized (and Foo will not be considered a serializer root).
The heuristics will discover types and members that satisfy all of the following criteria:
The type, or the declaring type of the member, is used
There must be a statically discoverable reference to the type. In other words, if running trimming tools without the serialization heuristics removes a given type, then the heuristics will not discover it or any of its members as a serialization root.
The type or member is attributed with a serializer-specific attribute.
See the sections below about the attributes you can use for each serializer. The attribute must be present on the root type or member, including fields/properties/methods/events, public or private, though the serializers may not define attributes that can be placed on all member kinds.
Note that passing a type directly to a serializer constructor is not enough to keep it. We do not use dataflow to discover types. For example:
new XmlSerializer (typeof (RootType)); // Will not consider RootType as one of the root types
This pattern will not consider the type passed into the constructor as a root type, even though it is statically analyzable in theory.
On any member supported by the attribute:
Xml*Attribute in the System.Xml.Serialization namespace
XmlIgnoreAttributeOn types:
System.Runtime.Serialization.DataContractAttributeOn properties, fields, or events:
System.Runtime.Serialization.DataMemberAttributeThe heuristics will consider the following types based on the discovered roots:
Starting with these types, the heuristics will recursively discover a set of types considered for serialization:
Note that the types of implemented interfaces are not necessarily discovered.
For each discovered type (including root types and the recursive type graph), if the corresponding serializer is active, trimming tools will mark the type and the following members:
Note that in general, private members and static members are not preserved, nor are methods or events (other than the mentioned constructor).
In addition, trimming tools mark:
Most features of reflection-based serializers will not work even with these heuristics. The following is an incomplete list of scenarios which will not work, unless the involved types are attributed as described above:
typeof(MyType) (directly or indirectly) into serializer constructors or methodsKnownTypeAttributeDataContractSerializer.KnownTypesextraTypes argument of the XmlSerializer ctorICollectioncollection interfaces into serializer-specific default types