IEnumerable<>支持使用'yield return'语句进行DataContract序列化

4

是否可能序列化一个IEnumerable属性,其中的值由“yield return”语句支持?如果可能的话,如何操作?如果不行,为什么?

每当我尝试这样做时,DataContractSerializer会抛出NullReferenceException异常。以下是一个例子:

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()
        {

        }
    }
}

异常详情:

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: 

这是在.NET 4.0中,运行于Visual Studio 2010环境下。


2
[来自StackOverflow的答案]在C#中,yield语句用于创建一个迭代器。当你使用yield return语句时,它会返回一个值并暂停执行方法,直到下一次调用迭代器时继续执行。这使得序列化变得更加复杂,因为序列化需要将整个对象图形转换为字节流。如果你想序列化一个包含yield语句的对象,你需要手动实现ISerializable接口,并在GetObjectData方法中处理所有的字段和属性。你需要确保在反序列化时正确地恢复迭代器状态。总之,序列化带有yield语句的对象是可能的,但需要额外的工作。 - Peter Kiss
2
我认为@PeterKiss的意思是 来自SO线程的回答 - M.Babcock
谢谢,我的帖子出了些问题,我无法修复它。 - Peter Kiss
嗯,不知怎么的,那个答案在我的尽职调查中被漏掉了。感谢提供链接。 - Caleb
1个回答

2
考虑以下序列:
var file = File.Open("a.txt");
yield return "";
//#1
yield return new StreamReader(file).ReadToEnd();

假设这个序列被枚举到第一步并被暂停。即使您能够将其序列化,您如何恢复它呢?DataContractSerializer 不能神奇地重新打开您的文件。
没有安全的方法来恢复/反序列化一个 yield-backed 序列,因为这样的序列可以执行任何操作。它可以打开消息框或格式化您的硬盘。
这就是为什么 C# 语言设计者没有在迭代器类上公开任何可用于序列化或反序列化它们的功能。
只是使用反射手动序列化迭代器类上的字段始终依赖于编译器实现细节。不适合生产环境。

在您的示例中,如果枚举被挂起,我期望反序列化结果只包含一个空字符串项。如果枚举没有被挂起,我期望ReadToEnd的输出将被序列化/反序列化,没有神奇的文件I / O涉及其中。此外,您能否详细说明一下反序列化yield-backed序列可能会打开消息框或格式化硬盘的说法?我不明白如何通过向IEnumerable添加操作来执行该操作。 - Caleb
在点#1处打开了一个文件句柄,该句柄在点#1之后使用。反序列化的行为在这里不是问题。问题是当我们重新启动序列(MoveNext到下一个项目)时会发生什么。您认为会发生什么?FileStream类没有能力在反序列化后自动重新打开自己。反序列化无法继续执行的内容是没有意义的。在许多情况下,这将非常危险,因此C#设计团队禁止了它。 - usr
虽然这在 .NET 4.0 中不起作用,但似乎在 .NET 4.5+ 中可以工作。 - Cocowalla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接