views:

257

answers:

4

Problem:

I have a class, say Foo, that implements an Id property. Foo must be serializable. Foo.Id should be initialized to a new GUID on initialization of Foo. Foo.Id should not be changable once it has been set. Deserialization will attempt to set the Foo.Id, so it must be made Public.

Private _Id As String=system.Guid.NewGuid.tostring
Public Property Id As String
 Get
  return _Id
 End Get
 Set(ByVal value As String)
  _Id = value
 End Set
End Property

or for c#ers

private string _Id = system.Guid.NewGuid().ToString();
public string Id {
 get { return _Id; }
 set { _Id = value; }
}

Solution Thoughts:

The only solution seems to be to throw a runtime exception when setting Foo.Id, but this will cause a problem during deserialization. So, somehow we must make sure that the exception is only being thrown when an attempt at Set Foo.Id is made outside of the serializer. Some kind of flag or something in the constructor?

Edit, Deserialization method ...

public static Foo DeserializeFromFile(string sFilespec)
{
 Xml.Serialization.XmlSerializer oSerializer = new Xml.Serialization.XmlSerializer(typeof(Foo));
 System.IO.FileStream oStream = new System.IO.FileStream(sFilespec, IO.FileMode.Open);
 Foo oObject = oSerializer.Deserialize(oStream);
 oStream.Close();
 return oObject;
}
+6  A: 

I'm not sure if I understand your problem but you can try implementing the ISerializable interface in your class to manually fine-tune the serialization / deserialization processes.

[Serializable]
public class YourClass : ISerializable
{    
    private Guid _Id = Guid.NewGuid();

    public string Id
    {
            get { return _Id; }
            private set { _Id = value; }
    }

    public YourClass() // Normal constructor
    {
       // ...
    }

    // This constructor is for deserialization only
    private YourClass(SerializationInfo information, StreamingContext context)
    {
        Id = (Guid)information.GetValue("Id", typeof(Guid)); // etc
    }

    void ISerializable.GetObjectData(SerializationInfo information,
        StreamingContext context)
    {
        // You serialize stuff like this
        information.AddValue("Id", Id, typeof(Guid)); 
    }
}

Also read up on the SerializationInfo class for more information on serializing and deserializing the most common types.

DrJokepu
From the fact that the OP is talking about properties (not fields), I assume they are talking about XmlSerializer. I don't think this answer is helpful.
Marc Gravell
@Marc Gravell: Yeah it seems like you're right. In my defence, the fact that he was talking about XmlSerializer wasn't apparent when I posted the answer, he only made that edit later.
DrJokepu
A: 

First of all, I don't think your code compiles. You need to cast to (Foo). You're also missing a "using" block:

public static Foo DeserializeFromFile(string sFilespec)
{
        Xml.Serialization.XmlSerializer oSerializer = new Xml.Serialization.XmlSerializer(typeof(Foo));
        using (System.IO.FileStream oStream = new System.IO.FileStream(sFilespec, IO.FileMode.Open)) {
            return (Foo) oSerializer.Deserialize(oStream);
        }
}

More importantly, you're out of luck here unless you implement the IXmlSerializable interface and do your own deserialization. When you deserialize the Id property, you can set the _Id field directly, without using the property.

John Saunders
+1  A: 

From the description I assume you are using XmlSerializer. The fact is that XmlSerializer lacks the granularity for this. It doesn't support callbacks (which would allow you to detect serialization), etc.

Options:

  • implement IXmlSerializable - lots of work (with XmlReader/XmlWriter), and easy to get wrong... don't do it unless you have to
  • use a different serializer (such as DataContractSerializer, which also supports private getters/setters or fields, but doesn't support full xml control) - for example see here
  • use a separate DTO; i.e. use your class with private setter in the app, and a separate (simpler) class (public get/set) for serialization, perhaps with an implicit conversion operator between them
  • make the get/set public and don't worry about it

I think I'd go for the DTO option; it retains the simple but complete formatting control over the xml, and isn't much work.

using System;
using System.Xml.Serialization;
[XmlType("foo"), XmlRoot("foo")]
public class FooDto {
    [XmlAttribute("bar")]
    public string Bar { get; set; }

    public static implicit operator Foo(FooDto value) {
        return value == null ? null :
            new Foo(value.Bar);
    }
    public static implicit operator FooDto(Foo value) {
        return value == null ? null :
            new FooDto { Bar = value.Bar };
    }
}
public class Foo {
    private readonly string bar;
    public Foo(string bar) { this.bar = bar; }
    public string Bar { get { return bar; } }
}
static class Program {
    static void Main() {
        Foo foo = new Foo("abcdefg");
        FooDto dto = foo;
        new XmlSerializer(dto.GetType()).Serialize(
            Console.Out, dto);
    }
}
Marc Gravell
lol.. you posted 11 mins earlier than me :) Didn't know you already posted while I was editing my answer and testing the sample. What a surprise!! You won, Marc :)
Chansik Im
A: 

The way Drjokepu described seems to be the right way of doing it. Similarly, if you have to use XmlSerialization for any reason/constraint/requirement, you should implement IXmlSerializable interface. However, implementing IXmlSerializable may not be as intuitive as implementing ISerializable.

In this case, you may want to try the following trick as a workaround :).

  1. Make Foo.Id property read-only
  2. Create a container object, Boo. Boo only includes the same type of member variables to be serialized. In this example, Boo will have Id property with public setter and getter. Boo should not include any member methods Foo has.
  3. Use Boo for xml serialization and deserialization and use Foo for handling business logic. logic.

See the sample code below:

using System;
using System.IO;
using System.Xml.Serialization;

namespace SerializationSample
{
    class Program
    {
        private const string STR_CtempSerilizationxml = @"c:\temp\Serilization.xml";
        static void Main(string[] args)
        {
            Foo foo = new Foo(Guid.NewGuid().ToString());
            Console.WriteLine("Foo.Id = " + foo.Id);

            SerializeToFile(foo, STR_CtempSerilizationxml);

            Foo fooDeserialized = DeserializeFromFile(STR_CtempSerilizationxml);

            Console.WriteLine("Foo.Id = " + fooDeserialized.Id);

        }

        private static void SerializeToFile(Foo foo, string sFilespec)
        {
            XmlSerializer iSerializer = new XmlSerializer(typeof(Boo));
            using (FileStream stream = new FileStream(sFilespec, FileMode.Create))
            {
                iSerializer.Serialize(stream, new Boo(foo));
            }
        }

        public static Foo DeserializeFromFile(string sFilespec)
        {
            XmlSerializer oSerializer = new XmlSerializer(typeof(Boo));
            using (System.IO.FileStream oStream = new FileStream(sFilespec, FileMode.Open))
            {
                return new Foo(((Boo)oSerializer.Deserialize(oStream)).Id);
            }
        }
    }

    public class Foo
    {
        private readonly string _Id;
        public string Id 
        { 
            get { return _Id; }
        }

        public Foo(string id) { _Id = id; }      
    }

    public class Boo
    {
        public string Id { get; set; }

        public Boo(Foo foo) { Id = foo.Id; }

        public Boo() { }
    }
}
Chansik Im