Deserializing object references using DataContractSerializer with CollectionDataContract?

StackOverflow https://stackoverflow.com/questions/22691815

  •  22-06-2023
  •  | 
  •  

I can't seem to figure out how to use the CollectionDataContract attribute properly as soon as object references are involved. Any help is greatly appreciated. Warning: The code example might seem a little long, but I haven't found a way to explain this any shorter, so please bear with me. (Update: Today I learned that foobar is evil. I stand corrected, so I have replaced all references to "foo" and "bar" with a more meaningful example. Hope this helps.)

Consider this simple flashcard interface for a hypothetical quiz app:

interface IFlashcard
{
    string Question {get; set;}
    string Answer {get; set;}
}

It is implemented by classes Flashcard and FlippedFlashcard. Instances of FlippedFlashcard are supposed to keep a reference to a corresponding instance of Flashcard in order to reverse Question and Answer as some of the cards may work in "Jeopardy mode":

[DataContract]
class Flashcard : IFlashcard
{
    [DataMember]
    public string Question { get; set; }
    [DataMember]
    public string Answer { get; set; }
}

class FlippedFlashcard : IFlashcard
{
    [DataMember]
    public Flashcard OriginalFlashcard;

    [DataMember]
    public string Question
    {
        get { return OriginalFlashcard.Answer; }
        set { OriginalFlashcard.Answer = value; }
    }

    [DataMember]
    public string Answer
    {
        get { return OriginalFlashcard.Question; }
        set { OriginalFlashcard.Question = value; }
    }
}

Let's define a serializable collection of objects conforming to IFlashcard...

[CollectionDataContract (ItemName = "Flashcard")]
class DeckOfFlashcards : List<IFlashcard>
{
}

...and fill it with some flashcards:

Flashcard f1 = new Flashcard() { Question = "What do you get when you multiply 6 by 7?", Answer = "42" };
FlippedFlashcard f2 = new FlippedFlashcard { OriginalFlashcard = f1 }; 
DeckOfFlashcards myDeck = new DeckOfFlashcards();
myDeck.Add(f1);
myDeck.Add(f2);
f1.Question = "What is the answer to life, the universe and everything?";
Console.WriteLine(f2.Answer); // >> What is the answer to life, the universe and everything?

Until now, everything works as I want it to work. When I change the Question in f1 from "What do you get when you multiply 6 by 7?" to "What is the answer to life, the universe and everything?", the Answer in f2 reflects this change correctly:

f1.Question = "What is the answer to life, the universe and everything?";
Console.WriteLine(f2.Answer); // >> What is the answer to life, the universe and everything?

I want to serialize this collection and keep the references between instances of Flashcard and FlippedFlashcard. Here is my attempt:

List<Type> types = new List<Type> { typeof(Flashcard), typeof(FlippedFlashcard) };
DataContractSerializer dcs = new DataContractSerializer(typeof(DeckOfFlashcards), types, 100, false, true, null);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create("Flashcards.xml", settings))
    dcs.WriteObject(writer, myDeck); // >> Question, Answer and OriginalFlashcard from f2 are serialized with "i:nil="true"

Unfortunately, the resulting Xml file lists Question, Answer and OriginalFlashcard from f2 as empty:

<?xml version="1.0" encoding="utf-8"?>
<DeckOfFlashcards xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" z:Size="2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="http://schemas.datacontract.org/2004/07/Foobar">
  <Flashcard z:Id="2" i:type="Flashcard">
    <Answer z:Id="3">42</Answer>
    <Question z:Id="4">What is the answer to life, the universe and everything?</Question>
  </Flashcard>
  <Flashcard z:Id="5" i:type="FlippedFlashcard">
    <Answer z:Ref="4" i:nil="true" />
    <OriginalFlashcard z:Ref="2" i:nil="true" />
    <Question z:Ref="3" i:nil="true" />
  </Flashcard>
</DeckOfFlashcards>

When I try to deserialize the collection from the file...

DeckOfFlashcards myOtherDeck;
using (var reader = XmlReader.Create("Flashcards.xml"))
    myOtherDeck = (DeckOfFlashcards)dcs.ReadObject(reader); // >> fires NullReferenceException at "set { OriginalFoo.Bar2 = value; }" with value "What is the answer to life, the universe and everything?"

...I get a NullReferenceException, probably when the Xml for the FlippedFlashcard is hit.

Does anybody have any idea?

有帮助吗?

解决方案

Got it!

In order to prevent the NullPointerException, it was necessary to review the [DataContract] and [DataMember] attributes. interface IFlashcard needs no contract at all:

interface IFlashcard
{
    string Question { get; set;  }
    string Answer { get; set; }
}

The code for class Flashcard remains unchanged, just as I wrote it in the description. The only real change necessary was to remove the [DataMember]attributes from Question and Answer in class FlippedFlashcard, like this:

[DataContract]
class FlippedFlashcard : IFlashcard
{
    [DataMember]
    public Flashcard OriginalFlashcard { get; set; }

    public string Question
    {
        get { return OriginalFlashcard.Answer; }
        set { OriginalFlashcard.Answer = value; }
    }

    public string Answer
    {
        get { return OriginalFlashcard.Question; }
        set { OriginalFlashcard.Question = value; }
    }
}

Now everything works fine. :-)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top