More on XAML Serialization

I’m just finishing off the big refactor at work, moving from XML serialization to XAML serialization.  Part of the refactor covered some needed code changes that impacted serialization; specifically, I had to convert a pseudo-collection object into a true collection.  Everything worked fine, but when I tested serialization it failed on the deserialize side.  It took me a friggen day and a half to weed out what was going on (thanks, IAddChildInternal!).  I record it here so that the engineers at Microsoft can copy it into a Word doc and send it to people looking for some tech support (see the update at the bottom of that post, heh heh).

The Problem

Custom collection serialization to and from XAML.  Sometimes it works, sometimes it doesn’t.  When it works, its great.  When it doesn’t, you get cryptic error messages that seem to be designed to drive you crazy, like “Cannot add object of type System.String to property of type System.String.”  Specifically, for collections, you’ll get an error message like

“Cannot add content of type MyClass to an object of type MyClassCollection.  Error at object MyClass”

Note the feelings of frustration at being told that a class can’t be added to its strongly typed collection.  The whole PURPOUSE of the collection is to hold instances of that exact object!  And now feel the RAGE as you see that the error is at the object being added to the collection, and not the collection itself.  How can that possibly be?  the MyClass object isn’t being asked to do ANYTHING.  Shouldn’t the error be in the MyClassCollection?  Or at least in the XamlReader itself???

The Cause

After spending a good day trying to figure this one out, I dusted off the ol’ source server and debugged down into the framework.  Waaaaay down deep inside, within the BamlRecordReader.SetPropertyValueToParent method, I saw that the reader was trying to figure out how to add children to my collection.  First, it checked to see if the collection was an IDictionary, then it checked to see if it was an IList.  Next, it checked to see if it was an ArrayExtension, and finally it checked to see if it implemented IAddChild.  Eureka!  I have to be one of those four things in order to have child elements added to me!

The Solution (Almost)

My collection wasn’t a dictionary or an ArrayExtension, so my choices were to implement IList or IAddChild.  Well, looking at the two interfaces, the obvious choice was IAddChild.  It has two methods, AddChild and AddText.  I quickly implemented AddChild and threw a NotSupportedException in AddText.  Simple.  But my tests still failed! I debugged through the framework and saw that, even though I implemented IAddChild, the reader wouldn’t recognize such.  A little more digging and I saw that, rather than using the simple “if (parent is IAddChild)” type test, the reader was examining the parent object’s “context,” performing a bit flag comparison on a “ReaderFlags” property set somewhere in the call chain.

A bit more research on the subject (now that I knew where to look) lead me to the link posted above (first one in this post), which revealed that IAddChild is being weeded out of the framework.  Or at least as much as is possible when you’ve released an interface out in the wild.  The REAL interface being tested for is IAddChildInternal, which is not available to anybody outside of the framework.  Bugger.

The Solution

That means I was limited to implementing IList.  Its a relatively large interface, and what’s more annoying is that the reader only needs IList.Add to function.  On the bright side, you can implement the interface explicitly, which means that all that non-type-safe goo can be hidden from general view.  If your custom collection implements the generic IList interface, its pretty simple to just call your type safe methods from within the non-type safe versions.  For the stuff that you don’t want to implement (like, say, ICollection.SyncRoot) you can just throw a NotSupportedException.

 

Some Code

I’ll include a link to a sample project at the end of this post.  In the sample, there are two strongly typed collections that extend the generic IList interface.  One also explicitly implements IList; the other does not.  Here’s a fun game; figure out which one does and which one doesn’t!

<span style="font-weight: bold; color: #2f2fff;">public </span><span style="font-weight: bold; color: #2f2fff;">interface </span><span style="color: #01a6e4;">IMyInterface</span><span> { }</span><span style="font-family: consolas; font-size: 12pt;"> </span>

<span style="font-weight: bold; color: #2f2fff;">public </span><span style="font-weight: bold; color: #2f2fff;">class </span><span style="color: #2b91af;">MyClass</span><span> : </span><span style="color: #01a6e4;">IMyInterface</span><span> { }</span><span style="font-family: consolas; font-size: 12pt;"> </span>

<span style="font-weight: bold; color: #2f2fff;">public </span><span style="font-weight: bold; color: #2f2fff;">class </span><span style="color: #2b91af;">MyInterfaceCollection</span><span> : </span><span style="color: #01a6e4;">IList</span><span style="color: fuchsia;">&lt;</span><span style="color: #01a6e4;">IMyInterface</span><span style="color: fuchsia;">&gt;</span><span>, </span><span style="color: #01a6e4;">IList</span>

<span style="font-family: consolas; font-size: 12pt;">{</span>

<span>    </span><span style="color: green;">// see the download for implementation</span>

<span>}</span>


<span style="font-weight: bold; color: #2f2fff;">public</span><span style="font-weight: bold; color: #2f2fff;">class</span><span style="color: #2b91af;">MyInterFAILCollection</span><span> : </span><span style="color: #01a6e4;">IList</span><span style="color: fuchsia;">&lt;</span><span style="color: #01a6e4;">IMyInterface</span><span style="color: fuchsia;">&gt;</span>

<span>{</span>

<span>    </span><span style="color: green;">// see the download for implementation</span>

<span>}</span>

I’ll skip the serialization and deserialization methods; if you care (and you should, as the serializer is pretty sweet) you can check the code download.  The test for these classes is pretty simple:

<span>[</span><span style="color: #2b91af;">TestMethod</span><span>]</span>

<span style="font-weight: bold; color: #2f2fff;">public</span><span style="font-weight: bold; color: #2f2fff;">void</span><span style="color: navy;">Constructor_and_properties</span><span>()</span>

<span>{</span>

<span>    </span><span style="font-weight: bold; color: #2f2fff;">var</span><span style="color: navy;">win</span><span style="color: fuchsia;">=</span><span style="font-weight: bold; color: #2f2fff;">new</span><span style="color: #2b91af;">MyInterfaceCollection</span><span>() { </span><span style="font-weight: bold; color: #2f2fff;">new</span><span style="color: #2b91af;">MyClass</span><span>(), </span><span style="font-weight: bold; color: #2f2fff;">new</span><span style="color: #2b91af;">MyClass</span><span>() };</span>

<span>    </span><span style="font-weight: bold; color: #2f2fff;">var</span><span style="color: navy;">fail</span><span style="color: fuchsia;">=</span><span style="font-weight: bold; color: #2f2fff;">new</span><span style="color: #2b91af;">MyInterFAILCollection</span><span>() { </span><span style="font-weight: bold; color: #2f2fff;">new</span><span style="color: #2b91af;">MyClass</span><span>(), </span><span style="font-weight: bold; color: #2f2fff;">new</span><span style="color: #2b91af;">MyClass</span><span>() };</span>


<span>    </span><span style="font-weight: bold; color: #2f2fff;">var</span><span style="color: navy;">ser</span><span style="color: fuchsia;">=</span><span style="color: navy;">Serialize</span><span>(</span><span style="color: navy;">win</span><span>);</span>

<span>    </span><span style="font-weight: bold; color: #2f2fff;">var</span><span style="color: navy;">foo</span><span style="color: fuchsia;">=</span><span style="color: navy;">Deserialize</span><span>(</span><span style="color: navy;">ser</span><span>);</span>


<span>    </span><span style="color: navy;">ser</span><span style="color: fuchsia;">=</span><span style="color: navy;">Serialize</span><span>(</span><span style="color: navy;">fail</span><span>);</span>

<span>    </span><span style="font-weight: bold; color: #2f2fff;">var</span><span style="color: navy;">kaboom</span><span style="color: fuchsia;">=</span><span style="color: navy;">Deserialize</span><span>(</span><span style="color: navy;">ser</span><span>);</span>

<span>}</span>

This works great until we get to kaboom.  At which point it explodes with the following error:

Test method TestProject2.UnitTest1Test.Constructor_and_properties threw exception:  System.Windows.Markup.XamlParseException: Cannot add content of type ‘TestProject2.MyClass’ to an object of type ‘TestProject2.MyInterFAILCollection’.  Error at object ‘TestProject2.MyClass’, Line 2 Position 4..

No!  Bad exception message!  Bad!  The error isn’t at MyClass.  Its in the definition of the collection!  Sigh.  What a pain.

TL;DR

If you are serializing a collection to XAML and you get some weird error that doesn’t make sense, you must check to see if your collection implements IDictionary or IList.  If it doesn’t, find one that does.

Download the Sample Project