Follow up: Workaround for XmlSerializer assembly leaks
A while ago,
Paul Wilson and
Kirk Allen Evans reported that the
XmlSerializer is leaking assemblies
when the serializer object was instantiated with any of the
constructors but the most basic one. The simple
XmlSerializer constructor has logic to re-use the temporary
assemblies if it already built them for a given type. The
more complex constructors are missing that caching logic and
allow the temporary assemblies to leak if you don't keep the
serializer instance around in your program.
When I was looking through the
Product Feedback Center for Visual Studio 2005 (Whidbey)
the other day, I found out that the XmlSerializer assembly
leak is
not going to be fixed in Whidbey. Within a few days of me reading that,
John Bristowe was
asking why there isn’t anything in the
Mvp.Xml project to work
around that issue. And because I didn't have a real good
answer to that I sat down and wrote one, because there
should be.
The result is the XmlSerializerCache class. It's very easy
to use. You simply obtain XmlSerializer instances from the
various overloads of XmlSerializerCache.GetSerializer()
instead of the instantiating a serializer instance with the
XmlSerializer constructor. The signatures of the
GetSerializer() method match those of the XmlSerializer
constructor.
XmlSerializer ser = XmlSerializerCache.GetSerializer(
typeof( MyClass ), "http://www.mvpxml.org/mytool" );
The XmlSerializerCache canonicalizes the contents of the
parameter list to reduce the amount of serializer objects in
the application. For example, canonicalization recognizes
that an XmlSerializer instance created for this request:
XmlSerializerCache cache = new
XmlSerializerCache();
XmlSerializer ser1 = cache.GetSerializer( typeof(MyType),
new Type[] { typeof(TypeOne), typeof(TypeTwo) } );
is compatible with this request:
XmlSerializerCache cache = new
XmlSerializerCache();
XmlSerializer ser2 = cache.GetSerializer( typeof(MyType),
new Type[] { typeof(TypeTwo), typeof(TypeOne) } );
The GetSerializer method checks cache for compatible
instances that are compatible with the method parameters
before constructing a new instance to minimize the number of
created serializers. The compatibility check computes a
fingerprint from the method parameters. This computation
looks at every property of the passed in parameters. Yet
this operation is still less expensive than reflecting over
an object graph to create code and compile temporary
assemblies and it prevents "leaking" memory due to orphaned
assemblies that you cannot unload.
Parameter canonicalization is not the only feature of the
XmlSerializerCache. It also allows you to monitor what it's
doing, through raising events and via two performance
counters. Both counters are in the category
Mvp.Xml.XmlSerializerCache. Make sure you install the
Mvp.Xml with the .msi to create and delete the performance
counters.
The performance first counter "Cache Hits" exposes the
number of currently cached XmlSerializer instances. The
second counter "Cached Instances" reflects the number of
cache hits, i.e. how many times the instances were
successfully retrieved from the cache instead of creating a
new one.
In addition to the performance counters, the
XmlSerializerCache also features a pair of events to track
its operations from within the program that is using it. The
parameters of these events contain the parameter of the
request to the XmlSerializerCache's in case you want to
track or log what serializers are created for example.