2

Until last night, I've been parsing XML using a variety of libraries in .NET -- XmlDocument and XDocument mostly. I'm not sure why I didn't look into this sooner, but it occurred to me that there must be something available in .NET that gives you class serialization / deserialization for free, and of course that comes in the form of the XmlSerializer class.

Sure enough, by using a few lines of code, I was able to serialize and deserialize with ease (although in the code I'm currently writing, I only need to deserialize), and no longer had to take the few hours or so to write my own class to do this with other libraries, plus the requisite unit tests. But the problem is that I would like my properties to be read-only. If I make the setter private, then upon creation of the XmlSerializer I get this error:

Unable to generate a temporary class (result=1). error CS0200: Property or indexer 'MyProperty' cannot be assigned to -- it is read only

It looks like this is an issue that won't be resolved, so there must be a workaround.

Sure enough, I found this information, which indicates that you can get the code to compile if you give up auto properties and just back with private fields. Unfortunately, while this compiles, when you execute the code, it doesn't actually deserialize the data. After stopping my application, I noticed several entries in the Messages window that said this:

Could not find schema information for the element 'MyProperty'.

And this is because there's no code to assign a value to MyProperty, because XmlSerializer doesn't know how to deal with private fields!!!

I found an answer on StackOverflow that presents another solution, which is to use a DataContractSerializer which I hadn't heard of before. I made the necesary code changes to my class, but ended up with the same messages as above. I ran the code to be sure, and the class members don't get set when the XML is deserialized.

I'm thinking that in my particular case, I either suck it up and allow the members to get overwritten (bad), or I go back to my original way of doing things, which is to just write all of the serialization / deserialization code myself. Is there something I'm doing wrong here, or is it impossible to allow a class like XmlSerializer to set private members of a class during deserialization, while making the consumer of the class not be able to overwrite its members?

UPDATE: and yet another article that shows another way to do deserialization of private properties, but I just tried it and it also doesn't work.

Here are some examples of the class that I've tried to deserialize:

[Export]
[DataContract]
public class Configuration
{
    [DataMember(Name="Port")]
    private int _port;
    public int Port { get { return _port; }}
}

Result: when deserializing with the XmlSerializer, there are no errors, but _port and Port have a value of 0 when my XML file has a Port value of 1, e.g. <Port>1</Port>.

Another example:

[Export]
public class Configuration
{
    public int Port { get; set; }
}

Result: deserializes fine, but I don't want a public setter.

I deserialize the class like this:

XmlSerializer serializer = new XmlSerializer(typeof(Configuration));
FileStream reader = new FileStream( "config.xml", FileMode.Open);
Configuration Config = (Configuration)serializer.Deserialize( reader);
reader.Close();
Community
  • 1
  • 1
Dave
  • 13,797
  • 12
  • 80
  • 140

3 Answers3

3

I don't have a good answer, but I have several bad ones. In no particular order:

  1. You can create a pure data transfer object with r/w properties and use it for serialization/deserialization. You can then initialize your immutable business object by constructing with the DTO.

  2. The two serializer classes you mentioned also allow overriding their behavior through a combination of attributes and code. This is likely sufficient to solve your problem, but may well be more work than it's worth.

Steven Sudit
  • 18,659
  • 1
  • 44
  • 49
  • Thanks, Steven! I like #1. #2 not so much, but I guess it depends on the amount of massaging that's required to make it happen. :) – Dave Aug 07 '10 at 15:45
  • Yes, I think #1 is a bit better, since it works nicely with a layered architecture. I'm not a big fan of conflating the BO and DTO, as it either results in an overly promiscuous BO or an overly restrictive DTO. – Steven Sudit Aug 07 '10 at 15:47
  • Anyhow, don't mark this one as Accepted yet: there may well be a better answer out there. – Steven Sudit Aug 07 '10 at 15:47
  • @Steven I don't get it. From this article (http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/) it sounds like the DataContractSerializer *should* be able to deal with my private fields! Can you enlighten me a bit on this? – Dave Aug 07 '10 at 15:54
  • @Dave: If you explicitly declare `private` (but not `readonly`) backing fields for your read-only properties and flag those fields for serialization, it looks like it should work. – Steven Sudit Aug 07 '10 at 16:16
  • @Steven that's what I think I did. I didn't mark the class or fields for serialization, since from the examples I've looked at, it appears that just using the DataMemberAttribute makes that implicit. But I still can't get the data to deserialize... – Dave Aug 07 '10 at 23:15
  • @Dave: I think you would need to, at the very least, mark the class and selected fields with attributes. Then, as a test, fill it with values and serialize it out, to see if what it creates looks like what you intend to deserialize. – Steven Sudit Aug 08 '10 at 03:33
  • @Steven I may have mislead you there -- I didn't mark them as [Serializable], I marked them as [DataContractSerializer] and [DataMember]. I assumed that I didn't need to additionally mark them up with [Serializable]. – Dave Aug 08 '10 at 04:07
  • @Dave: That's correct, you only need the `DataContract` and `DataContractMember` attributes. – Steven Sudit Aug 08 '10 at 08:30
1

This isn't a question of which XML serialisation method to use, but about the serialisation itself (try it with a binary serialisation, and you'll get the same error).

If you cannot change the property to allow the default serialisation method to take place, then:

  1. Implement the ISerializable interface.
  2. In your GetObjectData implementation, add values to the SerializationInfo parameter passed for each field you need to record.
  3. Add a constructor (it need not be public) that takes a SerializationInfo parameter and a StreamingContext parameter. In this constructor you can read the data back from the SerializationInfo that was written in during serialisation, and hence produce the deserialised object.

Now, this may seem a bit like going back to manual serialisation, however it need only be done on the classes in quesiton, and it also makes your class serialisable by other mechanisms.

Jon Hanna
  • 102,999
  • 9
  • 134
  • 232
  • Right, this is the worst case of my #2 answer. – Steven Sudit Aug 07 '10 at 16:14
  • It doesn't change the XMLSerialiser behaviour, it changes the behaviour of the underlying reflection. At the end of the day, if a value is set to only be set during construction, then it can only be set during construction, and the solution... is to set it during construction. – Jon Hanna Aug 07 '10 at 16:35
  • @Jon thanks, that seems like good advice. If I end up having to manually do the deserialization (even though many examples seem to prove that this is not the case, yet I cannot do it), then I'll go the ISerializable route, which I haven't even done before, so it will be nice to learn yet another new topic. :) – Dave Aug 07 '10 at 23:18
  • You should certainly learn the ISerializable route anyway. One thing about ISerializable is that derived classes of ISerializable have to do so too (having the related constructor, and overriding GetObjectData if they have their own state). Since Exception implements ISerializable, any custom Exception that will leave an assembly is buggy if it doesn't do this (instead of receiving the right exception, the calling code will get a strange serialistion error). Your alternative approach, is to not have readonly properties. Really, you can't say "nothing changes this and then "please change this" – Jon Hanna Aug 07 '10 at 23:40
  • Incidentally, what were you doing before? Members that wrote XML and parsed it in a constructor? While I've done this before (lots, doing some right now in fact) and while it is strictly serialisation, I normally think of it as a different thing. Probably, because I have generally been coming at it as having the XML spec'd and building classes to match, rather than the opposite direction. Just an observation about how people think about things, in another language I'd call it serialisation, but since .NET has serialisation built-in, I don't call it that. – Jon Hanna Aug 07 '10 at 23:44
  • @Jon I only require deserialization in this particular case because the XML is for a hardware configuration file. If we need to ever generate a different XML file, we'll either have a util to do this, or we'll just take another config file and change the values. There's no need for the application to be able to write these values because it doesn't make sense. – Dave Aug 08 '10 at 00:05
  • @Jon I actually might not implement ISerializable. Upon researching it more, I came across an interesting point here on StackOverflow (http://stackoverflow.com/questions/810974/what-is-the-point-of-the-iserializable-interface). Seems that not bothering with that interface and either leaving everything public for now, or rolling the serialization "myself" (actually using XDocument) is the way to go. – Dave Aug 08 '10 at 06:06
  • It's not whether everything is public that is the matter (serialisation uses reflection to get at your private members), it's whether anything is readonly. Make readonly members non-readonly (they can remain read-only from the outside by not having setters) and ISerializable isn't needed. – Jon Hanna Aug 08 '10 at 10:51
  • @Jon none of my members have ever been readonly -- I've only tried private setters, but still can't deserialize. I'll give it another go when I wake up to start my day. :) – Dave Aug 08 '10 at 11:06
  • Ah, I think a private setter makes it treat it as readonly too, whereas a lack of setter (but being able to write to the underlying field on the inside does not). I must experiment with this more for my own curiousity, though at this point we're probably spending too much of your time talking about the issue and not enough just fixing it, given that you've three approaches that will get the job done! – Jon Hanna Aug 08 '10 at 12:05
  • @Jon Interesting -- I just tried this idea of yours, and I only specified a public getter, which returns the value of a private field marked with the [DataMember] attribute. No more errors, deserialization "succeeded", but none of the values from my XML file actually got set in the class. – Dave Aug 08 '10 at 19:09
  • Oh dear. At this point without much more detail about your class, I'm at a loss to answer you, I'm afraid :( – Jon Hanna Aug 08 '10 at 19:14
0

I think, ultimately, that the correct answer to this question is that if you need to deserialize (serialization isn't an issue) private data, then you want to avoid using XmlSerializer and DataContractSerializer. I still hope that I'm wrong, but in the end, Jon might be right -- I will need to either use ISerializable, or do it the way I've done it up until now.

I hit another brick wall tonight when I decided to use one class with public properties to allow deserialization, and then wrap it with another class. I didn't want to make the deserializable class public -- I wanted it to be a private class within the one that only exposes readonly properties. Well, the issue with that is that XmlSerializable can't deal with classes that are private.

I could just make the class public for now, which I might do just to get things working first, but I'm going to go a different route because having to make that class public just doesn't make sense.

Dave
  • 13,797
  • 12
  • 80
  • 140
  • Ok, can't get it to work even if I set it to public. Now I get an error message when deserializing -- `"InnerException = {" was not expected."}"`. Of course, I opened the XML file and there was xmlns there because I created the file with XmlSerializer. So I deleted the xmlns part and **still** get the same error. WTH??? – Dave Aug 08 '10 at 04:12
  • Rewrote it and it works fine with all public properties now. Not sure what went wrong before. – Dave Aug 08 '10 at 06:06