Invalid output with inherit class

谁说胖子不能爱 提交于 2020-01-03 08:55:09

问题


I have 2 classes

[DataContract, KnownType(typeof(B))]
public class A
{
    [DataMember]
    public string prop1 { get; set; }
    [DataMember]
    public string prop2 { get; set; }
    [DataMember]
    public string prop3 { get; set; }
}

[DataContract]
public class B : A
{
    [DataMember]
    public string prop4 { get; set; }
}

and the following method:

List<B> BList = new List<B>();
BList = new List<B>() { new B() { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" } };
List<A> AList = BList.Cast<A>().ToList();
DataContractSerializer ser = new DataContractSerializer(typeof(List<A>));
FileStream fs = new FileStream(@"C:\temp\AResult.xml", FileMode.Create);
using (fs)
{
    ser.WriteObject(fs, AList);
}

which writes this to the outcoming XML file:

<ArrayOfProgram.A xmlns="http://schemas.datacontract.org/2004/07/foo" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Program.A i:type="Program.B">
<prop1>1</prop1>
<prop2>2</prop2>
<prop3>3</prop3>
<prop4>4</prop4>
</Program.A></ArrayOfProgram.A>

How could it happen, that prop4 is within the result and how can I avoid this? prop4 is not part of List<A> which is being serialized.


回答1:


That happens because you are storing in AList the pointer to the B object instance. When you did "new B() { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" }" you created an B object instance. When the serializer reflects the object stored in AList, it finds an actual B object instance, because you didint change the B object instance, you only stored it in the AList. The compiler allowed you to do that because the inheritance chain permits it but the B object instance was not changed, then it is a B object instance no matters where you store it.

Instead of doing:

List<A> AList = BList.Cast<A>().ToList();

Do:

List<A> AList = BList.Select(b => new A() 
{ prop1 = b.prop1, prop2 = b.prop2, prop3 = b.prop3 })
.ToList();

That will create a new A instance for each B instance in BList




回答2:


The DataContractResolver allows you to customize how the DataContract can be resolved. In this case, you simply want the sub type to be resolved as the base type.

The following code is from this blog post.

https://blogs.msdn.microsoft.com/youssefm/2009/06/05/configuring-known-types-dynamically-introducing-the-datacontractresolver/

public class DeserializeAsBaseResolver : DataContractResolver
{
    public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
    }

    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? declaredType;
    }
}

Depending on the framework, pass the above class into the datacontract serializer and it should give you the results you need.

DataContractSerializer ser = new DataContractSerializer(typeof(List<A>));), null, Int32.MaxValue, false, false, null, new DeserializeAsBaseResolver ());



回答3:


A simple way to downcast in C# is to serialize the child and then deserialize it into the parent.

List<B> BList = new List<B>();
BList = new List<B>() { new B() { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" } };
var serializedChildList = JsonConvert.SerializeObject(BList);
List<A> AList = JsonConvert.DeserializeObject<List<A>>(serializedChildList);
DataContractSerializer ser = new DataContractSerializer(typeof(List<A>));
FileStream fs = new FileStream(@"C:\temp\AResult.xml", FileMode.Create);
using (fs)
{
    ser.WriteObject(fs, AList);
}

sample output:

<ArrayOfA xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <A>
    <prop1>1</prop1>
    <prop2>2</prop2>
    <prop3>3</prop3>
  </A>
</ArrayOfA>



回答4:


If you haven't done it yourself yet as mentioned in one of the comments, here is a little code I fiddled. I haven't worked with AutoMapper for a while, so I couldn't remember and work out how to map List<T> types. Anyways, here is the fiddle:

var list = new List<B> { new B { prop1 = "1", prop2 = "2", prop3 = "3", prop4 = "4" } };
Mapper.Initialize(i => i.CreateMap<B, A>());
using (var stream = new FileStream(@"output.xml", FileMode.Create))
{
    var serializer = new DataContractSerializer(typeof(List<A>));
    serializer.WriteObject(stream, list.Select(i => Mapper.Map<A>(i)).ToList());
}


来源:https://stackoverflow.com/questions/39613957/invalid-output-with-inherit-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!