4

Is it possible to serialize an IEnumerable property where the values are backed by 'yield return' statements? If it's possible, how? It not, why?

I'm getting a NullReferenceException from the DataContractSerializer whenever I attempt to do so. An example:

using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;

namespace IEnumerableTest
{
    class Program
    {
        static void Main(string[] args)
        {
            DataContractSerializer ser =
                new DataContractSerializer(typeof(Test));

            using (FileStream writer = new FileStream("test.xml", FileMode.Create))
            {
                Test test = new Test();
                //NullReferenceException thrown by the next call
                //if YieldValues is flagged as [DataMember]
                ser.WriteObject(writer, test);
            }
        }
    }

    [DataContract(Name = "Test")]
    public class Test
    {
        List<int> values = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8 };

        //This property serializes without issue
        [DataMember]
        public IEnumerable<int> Values
        {
            get
            {
                return values;
            }
        }

        //Attempting to serialize this member results in a NullReferenceException
        [DataMember]
        public IEnumerable<int> YieldValues
        {
            get
            {
                foreach (int value in values)
                {
                    yield return value;
                }
            }
        }

        public Test()
        {

        }
    }
}

Exception detail:

System.NullReferenceException was unhandled
  Message=Object reference not set to an instance of an object.
  Source=System.Runtime.Serialization
  StackTrace:
       at System.Runtime.Serialization.XmlObjectSerializerWriteContext.OnHandleIsReference(XmlWriterDelegator xmlWriter, DataContract contract, Object obj)
       at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiType(XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle objectTypeHandle, Type objectType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
       at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
       at WriteTestToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
       at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
       at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
       at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
       at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
       at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
       at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
       at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
       at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(Stream stream, Object graph)
       at IEnumerableTest.Program.Main(String[] args) in F:\Users\Caleb\Documents\Visual Studio 2010\Projects\IEnumerable\Program.cs:line 19
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

This is in .NET 4.0, running inside of Visual Studio 2010

Caleb
  • 289
  • 3
  • 17
  • 2
    [Answer from StackOverflow thread][1] [1]: http://stackoverflow.com/questions/3294224/c-sharp-serialization-and-the-yield-statement – Peter Kiss Feb 29 '12 at 21:03
  • 2
    I think @PeterKiss is trying to say, [Answer from SO Thread](http://stackoverflow.com/questions/3294224/c-sharp-serialization-and-the-yield-statement). – M.Babcock Feb 29 '12 at 21:09
  • Thanks, something went wrong with my post and i can't fix it. – Peter Kiss Feb 29 '12 at 21:11
  • Blah, somehow that answer slipped through my due diligence searches. Thanks for the link. – Caleb Feb 29 '12 at 21:35

1 Answers1

2

Consider the following sequence:

var file = File.Open("a.txt");
yield return "";
//#1
yield return new StreamReader(file).ReadToEnd();

Imagine this sequence is enumerated to the point #1 and suspended. Even if you were able to serialize it, how would you ever restore it? The DataContractSerializer cannot magically reopen your file.

There is no safe way to restore/deserialize a yield-backed sequence because such a sequence could do anything. It could open a message box or format your hard disk.

That is why the C# language designers did not expose any functionality on their iterator classes that can be used to serialize or deserialize them.

Just manually serializing the fields on an iterator class using reflection would always rely on compiler implementation details. Not production-ready.

usr
  • 168,620
  • 35
  • 240
  • 369
  • In your example, I would expect the deserialized result to simply contain a single blank string item if the enumeration was suspended. If the enumeration wasn't suspended, I would expect the output of ReadToEnd to be serialized/deserialized, no magical file I/O involved. Also, can you elaborate on your statement that deserializing yield-backed sequence could open a message box or format a hard drive? I don't see how adding an action to a IEnumerable could cause that action to be executed. – Caleb Mar 05 '12 at 17:25
  • At point #1 there is a file handle open which is used after point #1. The act of deserialization is not the problem here. The problem is what happens when we restart the sequence (MoveNext to the next item). What would happen in your opinion? The FileStream class has no capability to magically reopen itself after deserialization. There is no _point_ deserializing stuff that cannot continue to execute. It would be very dangerous in many circumstances so the C# design team disallowed it. – usr Mar 05 '12 at 18:20
  • While this doesn't work in .NET 4.0, it does seem to work in .NET 4.5+ – Cocowalla Aug 31 '16 at 21:00