SerializableAttribute and ISerializable support dropped from MonoGame 3.2

I want to start off by saying I really enjoy working with MonoGame. C# is my language of choice and it has some awesome features that work extremely well for designing games. The only real alternative is Unity. Unity is a great framework/engine too, but unfortunately it is kind of limiting in some aspects and only really behaves if you, “Do it the Unity Way”. MonoGame on the other hand gives you the freedom to “roll your own”. I fully support what is being done here and I hope this post is not taken negatively.

That being said, I feel that the decision detailed here to remove the [Serializable] attribute and ‘ISerializable’ interface in favor of the [DataContract]/[DataMember] attributes was made a bit too hastily. It seems like it was made in the sense of, “Does it still work?” rather than one that considers, "Does this improve MonoGame as a foundation for making games?

I want to propose a hypothetical case study where an engineer is tasked with implementing a Component<-Object System for his Team’s game.

To start, he puts together a basic container type to represent an object within his game. It supports adding and removing ‘Components’ and ‘RenderableComponents’ much like the ‘GameComponent’ and ‘DrawableGameComponent’ within XNA. It smells nice too!

GameObject.cs:

[DataContract(IsReference=true)]
public sealed class GameObject
{
        /// <summary>
        /// The name of the GameObject.
        /// </summary>
        [DataMember(EmitDefaultValue=true)]
        public string Name { get; set; }

        /// <summary>
        /// The position of the GameObject
        /// </summary>
        [DataMember(IsRequired=true)]
        public Vector3 Position { get; set; }

        /// <summary>
        /// A collection of Components that describe
        /// the behaviour of the GameObject
        /// </summary>
        [DataMember(IsRequired=true)]
        public HashSet<Component> Components { get; set; }

        /// <summary>
        /// A collection of components that require
        /// themselves to be rendered providing 
        /// a visual representation of our GameObject
        /// </summary>
        [DataMember(IsRequired=true)]
        public HashSet<RenderableComponent> RenderableComponents { get; set; }

        /// <summary>
        /// The default constructor for a GameObject;
        /// </summary>
        public GameObject()
        {
            this.Name = "GameObject";
            this.Position = Vector3.Zero;
            this.Components = new HashSet<Component>();
            this.RenderableComponents = new HashSet<RenderableComponent>();
        }

        /// <summary>
        /// Adds a component to our GameObject
        /// </summary>
        /// <param name="component">The component instance to be added to this GameObject.</param>
        /// <returns>When TRUE, the component was successfully added; otherwise FALSE.</returns>
        public bool AddComponent(Component component)
        {
            if(component == null)
                throw new ArgumentNullException("component");

            bool result = false;

            component.MyGameObject = this;
            result = this.Components.Add(component);

            if(typeof(RenderableComponent).IsAssignableFrom(component.GetType().UnderlyingSystemType))
                result = result && this.RenderableComponents.Add(component as RenderableComponent);

            return result;
        }

        /// <summary>
        /// Removes a component from our GameObject.
        /// </summary>
        /// <param name="component">
        /// The component instance 
        /// to be removed from our GameObject.
        /// </param>
        /// <returns>
        /// When TRUE, the component was 
        /// contained within this GameObject and 
        /// successfully removed; otherwise FALSE.</returns>
        public bool RemoveComponent(Component component)
        {
            if(component == null)
                throw new ArgumentNullException("component");

            bool result = false;

            component.MyGameObject = null;
            result = this.Components.Remove(component);

            if(typeof(RenderableComponent)
              .IsAssignableFrom(
               component.GetType().UnderlyingSystemType))
                result = result && this.RenderableComponents.Remove(component as RenderableComponent);

            return result;
        }
    }
}

Now he sets off to design a simple ‘Component’ class. It is aware of the GameObject that it represents, and even provides the ability to be updated. Joy!
Component.cs:

        [DataContract(IsReference = true)]
        public abstract class Component
        {
            /// <summary>
            /// The GameObject that we belong to.
            /// </summary>
            [DataMember(IsRequired=true)]
            public GameObject MyGameObject { get; set; }
            
            /// <summary>
            /// A method that our concrete implementation s
            /// will use to update themselves.
            /// </summary>
            public abstract void Update();
        }

The engineer ponders, “What good are invisible objects though? We need a way to render them to screen!”. So he extends his shiny new component class to also support draw calls.

RenderableComponent.cs

    [DataContract(IsReference=true)]
    public abstract class RenderableComponent : Component
    {
        /// <summary>
        /// A method that our concrete implementations
        /// will use to draw themselves.
        /// </summary>
        public abstract void Draw();
    }

The first problem with [DataContract] begins to surface at this point.

When he attempts to serialize the collection of ‘Compoent’ classes within the game object, the components that derrive from ‘RenderableComponent’ have violated the DataContract. He now has to let his ‘Component’ class know about the ‘RenderableComponent’ class via the [KnownType] attribute essentially binding the design of the two together.

His ‘Component’ class definition now looks like this:

[DataContract(IsReference = true)]
[KnownType(typeof(RenderableComponent))]
public abstract class Component
{
...
}

He thinks to himself, “Well thats not too bad I suppose. Contracts do tend to be very binding and all…” and has a good laugh :wink:

Ok now that he has completed those classes its time to start making the game!
He starts by defining some concrete classes to work with that all extend from ‘Component’ or ‘RenderableComponent’:

  • Mesh
  • Animated Mesh
  • AIController
  • SpaceShipController
  • ThrusterLogic
  • ParticleSystem
  • CollisionMesh
  • RocketLauncherLogic

Now lets check out our Base class definitions again. See how they are holding up.

Component.cs:

[DataContract(IsReference = true)]
[KnownType(typeof(RenderableComponent))]
[KnownType(typeof(Mesh))]
[KnownType(typeof(AnimatedMesh))]
[KnownType(typeof(AIController))]
[KnownType(typeof(SpaceShipController))]
[KnownType(typeof(ThrusterLogic))]
[KnownType(typeof(ParticleSystem))]
[KnownType(typeof(CollisionMesh))]
[KnownType(typeof(RocketLauncherLogic))]
public abstract class Component
{
...
}

RenderableComponent.cs:

[DataContract(IsReference = true)]
[KnownType(typeof(Mesh))]
[KnownType(typeof(AnimatedMesh))]
[KnownType(typeof(ParticleSystem))]
public abstract class RenderableComponent
{
...
}

This is quickly spiraling out of control…

What serialization using DataContract does is just that. It holds you to a very explicit contract in how your data can be serialized. Its core target was and always will be enterprise development, not games. DataContract binds abstractions to their concrete implementations which is kind of like slapping your mother… you just don’t do it.

To be fair, [KnownTypes] does allow you to specify a string that correlates to the name of a method within your type. This method is expected to return an IEnumerable containing all the ‘Known Types’ for the DataContract. Again though, we are talking about games here. Does your update loop have time to serialize your entire SceneGraph, reflect over all of the currently loaded assemblies, reflect over all the types in those assemblies, navigate up their inheritance chain, and determine if they are assignable from your base component class? In my opinion this is not how serialization should be handled within a game framework.

Off the top of my head a better way to tackle the IEnumerable issues would be to construct a cache when loading the game, dependencies, mods, etc by reflecting over them and determining the appropriate DataContract they are bound to. Then you would have the method specified in the KnownTypesAttribute return the cached list of types. However, this still is extremely cumbersome and isn’t really game-centric which is the entire point of a framework. In my opinion a framework should provide a set of tools that makes making games more about making games than reflecting over assemblies and pre-constructing a DataContract cache.

Again, I really like MonoGame Framework. Its great to work with and I fully support the project and those working on it. Everyone makes mistakes(especially myself) and I don’t mean to offend or step on anyone’s toes. My intentions are to help this project continue to grow and move in the right direction.

If I am mistaken in my observations about the design ramifications of this decision I sincerely apologize and would love to know the better-way to handle serialization in the current build(3.2).

Best Regards,
-K

I don’t think anyone’s thinking about it in terms of right and wrong. The attributes are just sentinels, for the use of whichever serializer you care to use. Personally, I’ve been working with XNA and MonoGame for years and this is the first time I’ve heard of anyone even using Serializable. It could be added back, but I suspect you would lose the ability to port to some platforms (Windows Store in particular) if you did use it.

I wasn’t really clear on why you want Serializable (specifically) back. Were you looking to use BinaryFormatter? I also despise marking base classes with KnownType, but there are a bunch of ways to avoid it so I’ve never had to.

For instance, assuming you’re using DataContractSerializer and it’s having trouble finding your types, you can keep a list of those types in a class at the game level where it has access to both game and XNA types. You simply pass all those types into one of DataContractSerializer’s constructor overloads, and it should be able to resolve everything. It’s still kinda smelly, but the smell is contained to a relatively small area.

You could also look into using a tagged union, but that’s less C#-like and probably better suited for when the classes you’re serializing are in a separate hierarchy from the ones you use at runtime.

Really if I had my way we would drop any built in form of attribute based serialization all together. It works in simple cases giving you a false sense of having the problem solved, but becomes a huge mess late in development or when patching the game.

IMO the best path is to design your own attributes and serializer that fits your specific needs.