You Can't XmlSerialize Shadowed Properties Very Well
This is something new I learned tonight. It make sense when you think about it I guess, but it bit us at work (luckily, not directly my fault this time :) but something I had to find an answer to since I'm the XMLSerialization expert there.
Oh who am I kidding?? I'm the everything expert there. I have so much knowledge inside my head that absolutely everyone comes to me for help because there's nothing I can't do. No one knows more than me. And I'm always right.
...
*blink*
Oh. Sorry. Got a little carried away there. Delusions of grandeur and all that.
*cough*
Anyway, I thought I'd write down a trivial example of what happens, for future reference. This is extremely trivial, and probably not something you'd implement for real. But it gets the error to occur, so it'll do.
First, consider ClassA:
Public Class ClassA
Private msThing1 As String
Public Property Thing1() As String
Get
Return msThing1
End Get
Set(ByVal Value As String)
msThing1 = Value
End Set
End Property
End Class
A simple property, sets and gets a string. Now we have ClassB, which inherits from ClassA:
Public Class ClassB
Inherits ClassA
Public Shadows Property Thing1() As Int32
Get
Return System.Convert.ToInt32(MyBase.Thing1)
End Get
Set(ByVal Value As Int32)
MyBase.Thing1 = Value.ToString
End Set
End Property
End Class
We've wrapped the Thing1 String property in a shadowing Thing1 Int32 property. Nice huh? Very handy!
Next we need to serialize these classes for some reason. So we create a serializer based on those types and see what happens.
What happens? BOOM! She 'sploded! Here's my test code:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim oSerializer As System.Xml.Serialization.XmlSerializer
Dim oBuilder As New StringBuilder
Dim oWriter As New StringWriter(oBuilder)
Try
oSerializer = New XmlSerializer(GetType(ClassA), New System.Type() {GetType(ClassB)})
oSerializer.Serialize(oWriter, New ClassA)
Debug.WriteLine(oBuilder.ToString)
oBuilder.Length = 0
oSerializer.Serialize(oWriter, New ClassB)
Debug.WriteLine(oBuilder.ToString)
Catch ex As Exception
Stop
End Try
End Sub
An exception gets thrown trying to create the XmlSerializer object. The two properties clash, for while one shadows the other, they're both public, and therefore serializable.
Avoiding the problem is tricky, depending on how you need to use the classes. My first thought was to not bother actually serializing out ClassB's Thing1 property - after all, it only forwards on the call in the end to the base class, so the base classes version should be adequate for serialization. Right?
Wrong. If we modify ClassB to include an XmlIgnore() attribute:
Public Class ClassB
Inherits ClassA
<XmlIgnore()> _
Public Shadows Property Thing1() As Int32
Get
Return System.Convert.ToInt32(MyBase.Thing1)
End Get
Set(ByVal Value As Int32)
MyBase.Thing1 = Value.ToString
End Set
End Property
End Class
When the test code is run, we still die when creating the serializer, but this time with a much stranger error:
File or assembly name j0mgowdd.dll, or one of its dependencies, was not found. The name of the dll will change each time, but it's always some random name.
People who've banged their heads against the XmlSerializer before will have this baby many a time. What it normally means is that while the source code for the temp assembly was generated, the compiler failed to compile the code for some reason. So the temp assembly didn't get created, but the serializer still tried to instantiate it (that's how I read it, anyway). But what was the real error? With help from the amazing Sells Brothers XmlPreCompiler, we can discover:
ybstkcwp.0.cs(21,50): error CS0030: Cannot convert type 'int' to 'string'
ybstkcwp.0.cs(116,37): error CS0029: Cannot implicitly convert type 'string' to 'int'
So by XmlIgnoring the property in ClassB, some code was generated that assigned ClassA's string property to ClassB's int property - which happens because the property was still exists, even tho it's ignored at the same time. Get me?
There's three ways I've found to modify the code so that it works.
- Ignore ClassA's Thing1 property. But if you serialize a ClassA on it's own, you won't get the Thing1 property serialized at all.
- Hide ClassA's Thing1 property by making it non-public (Private, Protected, etc). See previous point.
- Rename ClassB's Thing1 property to something else. So much for OO patterns.
All of these cases, depending on the scenario in question, suck.
Me, I prefer the fourth option. Find a completely different way to get what you need. If you find yourself painted into this corner, any 'fix' is only a hack to make it through, and not a good solution overall. Think about what you're trying to achieve, and you'll possibly find a different way of achieving the same sort of outcome. In the problem we have at work, I'll definitely be recommending option 4 :)
Listening to: thinking in reverse - the dissociatives - (3:44)