Search code examples
c#.netserializationdynamicexpandoobject

Can I serialize an ExpandoObject in .NET 4?


I'm trying to use a System.Dynamic.ExpandoObject so I can dynamically create properties at runtime. Later, I need to pass an instance of this object and the mechanism used requires serialization.

Of course, when I attempt to serialize my dynamic object, I get the exception:

System.Runtime.Serialization.SerializationException was unhandled.

Type 'System.Dynamic.ExpandoObject' in Assembly 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.

Can I serialize the ExpandoObject? Is there another approach to creating a dynamic object that is serializable? Perhaps using a DynamicObject wrapper?

I've created a very simple Windows Forms example to duplicate the error:

using System;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Dynamic;

namespace DynamicTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {            
            dynamic dynamicContext = new ExpandoObject();
            dynamicContext.Greeting = "Hello";

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream("MyFile.bin", FileMode.Create,
                                           FileAccess.Write, FileShare.None);
            formatter.Serialize(stream, dynamicContext);
            stream.Close();
        }
    }
}

Solution

  • I can't serialize ExpandoObject, but I can manually serialize DynamicObject. So using the TryGetMember/TrySetMember methods of DynamicObject and implementing ISerializable, I can solve my problem which was really to serialize a dynamic object.

    I've implemented the following in my simple test app:

    using System;
    using System.Windows.Forms;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Security.Permissions;
    
    namespace DynamicTest
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {            
                dynamic dynamicContext = new DynamicContext();
                dynamicContext.Greeting = "Hello";
                this.Text = dynamicContext.Greeting;
    
                IFormatter formatter = new BinaryFormatter();
                Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None);
                formatter.Serialize(stream, dynamicContext);
                stream.Close();
            }
        }
    
        [Serializable]
        public class DynamicContext : DynamicObject, ISerializable
        {
            private Dictionary<string, object> dynamicContext = new Dictionary<string, object>();
    
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                return (dynamicContext.TryGetValue(binder.Name, out result));
            }
    
            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                dynamicContext.Add(binder.Name, value);
                return true;
            }
    
            [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
            public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                foreach (KeyValuePair<string, object> kvp in dynamicContext)
                {
                    info.AddValue(kvp.Key, kvp.Value);
                }
            }
    
            public DynamicContext()
            {
            }
    
            protected DynamicContext(SerializationInfo info, StreamingContext context)
            {
                // TODO: validate inputs before deserializing. See http://msdn.microsoft.com/en-us/library/ty01x675(VS.80).aspx
                foreach (SerializationEntry entry in info)
                {
                    dynamicContext.Add(entry.Name, entry.Value);
                }
            }
    
        }
    }
    

    and Why does SerializationInfo not have TryGetValue methods? had the missing puzzle piece to keep it simple.