user2079828
user2079828

Reputation: 655

C# DataContract and Serializing readonly bool field to always be 'true'

The below class is a contrived example. I would like the easiest way to serialize the 'isVisible' field to 'true' every single time it is serialized...regardless of what it is set to.

In the real code...this is a much larger object and has a few different constructors that are called from different points.

Right now, I can think of two ways.

  1. Create an 'OnSerializing()' method that used reflection to set the property to 'true' during serialization. (I'm not actually sure if this will work...but it seems like it would).

  2. In the method that initiates the serialization of the class...create all new items that have the isVisible as 'true' before serializing.

    [DataContract]
    public class MapItem
    {
        public MapItem(bool isVisible)
        {
            this.isVisible = isVisible;
        }
    
        [DataMember]
        private readonly bool isVisible;
    
        public bool IsVisible => isVisible;
    }
    

Why do I need this? Basically, I need to load in these items and have them always be visible directly after a load. While running the application...the MapItems may become visible/invisible. Thus I want the XML to always have <isVisible>true</isVisible> OR I want the value inside MapItem to be 'true' ONLY when deserializing.

Upvotes: 0

Views: 969

Answers (1)

dbc
dbc

Reputation: 116981

You would like the field private readonly bool isVisible; to be initialized to true during deserialization. Unfortunately, DataContractSerializer doesn't call any constructor of your class so there is no obvious, easy way to do this.

Your proposed solution -- of always emitting <isVisible>true</isVisible> during serialization and taking advantage of the data contract serializer's ability to deserialize read-only field values -- is less than ideal because a badly crafted XML file, e.g. one where <isVisible> is false or even missing, could introduce unexpected behavior into your application.

Instead, consider the following alternatives that do not require serializing the value of isVisible:

  1. Modify the name and semantics of the read-only field so that the default value is the correct, desired value. In this case you would need to replace the current isVisible with private readonly bool isNotVisible;:

    [DataContract]
    public class MapItem
    {
        // Do not mark with [DataContract] as deserialized instances should always have the default value
        private readonly bool isNotVisible; 
    
        public MapItem(bool isVisible)
        {
            this.isNotVisible = !isVisible;
        }
    
        public bool IsVisible { get { return !isNotVisible; } }
    }
    
  2. Set the field value in an [OnDeserializing] callback using reflection:

    [DataContract]
    public class MapItem
    {
        public MapItem(bool isVisible)
        {
            this.isVisible = isVisible;
        }
    
        // Do not mark with [DataContract] as deserialized instances should always have the value
        // set in the OnDeserializing() callback.
        private readonly bool isVisible;
    
        public bool IsVisible { get { return isVisible; } }
    
        [OnDeserializing]
        void OnDeserializing(StreamingContext context)
        {
            typeof(MapItem)
                .GetField(nameof(isVisible), BindingFlags.Instance | BindingFlags.NonPublic)
                .SetValue(this, true);
        }
    }
    
  3. Finally, you could implement ISerializable as this is supported by the data contract serializer, and initialize isVisible to true in the streaming constructor:

    [Serializable]
    public class MapItem : ISerializable
    {
        public MapItem(bool isVisible)
        {
            this.isVisible = isVisible;
        }
    
        private readonly bool isVisible;
    
        public bool IsVisible { get { return isVisible; } }
    
        #region ISerializable Members
    
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
        }
    
        #endregion
    
        protected MapItem(SerializationInfo info, StreamingContext context)
        {
            this.isVisible = true;
        }
    }
    

    However this is not recommended as you now would need to serialize all of your class's members manually.

Upvotes: 1

Related Questions