如果在类中反序列化接口属性,如何定义采用哪个类?

发布于 2024-09-02 00:20:35 字数 1033 浏览 10 评论 0原文

想象一下您有以下类,

[DataContract]
public class NamedList
{
    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public IList<string> Items { get; private set; }

    public DumpList(string name)
    {
        Name = name;
        Items = new List<string>();
    }
}

如果将其序列化到文件中,则非常容易,因为 IList 背后的具体类是已知的并且可以序列化。

但是如果您尝试将此文件反序列化回内存中会发生什么?
它可以正常工作,不会发生任何直接错误。

如果您尝试在列表中添加或删除某些内容,就会出现问题。在这种情况下,你会得到一个例外。这个异常的根源来自于反序列化对象使用数组作为 IList 的具体实现的情况。

在这个简单的例子中避免这个问题很容易。只需序列化具体的后备存储而不是公共属性,并在构造函数中进行更改:

[DataMember(Name = "Items")]
private List<string> _Items;

public IList<string> Items
{
    get
    {
        return _Items;
    }
}

public DumpList(string name)
{
    Name = name;
    _Items = new List<string>();
}

但更有趣的问题是:

  • 为什么选择 Deserializer Array 类型作为 IList 接口的具体实现?
  • 是否可以更改每个接口应采用哪个类的设置?
  • 如果我有一个自定义的接口和该接口的多个实现,是否可以告诉反序列化器应该为给定的接口采用哪个具体类?

Just imagine you have the following class

[DataContract]
public class NamedList
{
    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public IList<string> Items { get; private set; }

    public DumpList(string name)
    {
        Name = name;
        Items = new List<string>();
    }
}

If you serialize this into a file, it is quite easy, cause the concrete class behind the IList is known and can be serialized.

But what happens if you try to deserialize this file back into memory?
It works without any direct error occuring.

The problem comes if you try to add or remove something from the list. In that case you'll get an exception. And the root of this exception comes from the case that the deserialized object uses as concrete implementation for the IList an Array.

To avoid this problem in this simple example is easy. Just serialize the concrete backing store instead of the public property and make the change in the constructor:

[DataMember(Name = "Items")]
private List<string> _Items;

public IList<string> Items
{
    get
    {
        return _Items;
    }
}

public DumpList(string name)
{
    Name = name;
    _Items = new List<string>();
}

But the more interesting question is:

  • Why chooses the Deserializer the Array type as concrete implementation of the IList interface?
  • Is it possible to change the settings which class should be taken for each interface?
  • If i have a self defined interface and several implementations of this interface, is it possible to tell the Deserializer which concrete class should be taken for a given interface?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

会发光的星星闪亮亮i 2024-09-09 00:20:35

您可以使用 DataContractSurrogate 进行反序列化来解决此问题,将 IList 替换为 List。

public class CustomDataContractSurrogate : IDataContractSurrogate
{
    // The only function you should care about here. The rest don't do anything, just default behavior.
    public Type GetDataContractType(Type type)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(ICollection<>)))
        {
            return (typeof(List<>).MakeGenericType(type.GetGenericArguments().Single()));
        }
        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        return obj;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        return obj;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return typeDeclaration;
    }
}

基本上就是这样,您只需要使用该代理创建 DataContractSerializer 实例并将其用于反序列化(对于序列化来说并不重要),例如:

var serializer = new DataContractSerializer(type, new Type[]{}, Int32.MaxValue, false, true, new CustomDataContractSurrogate());

或任何其他采用代理的构造函数。

或者,(作为答案的奖励)如果您正在使用 app/web.config 定义的服务,您可以定义一个自定义行为,使用上述代理创建数据协定序列化程序:

public class CustomDataContractSerializerBehavior : DataContractSerializerOperationBehavior
{
    public CustomDataContractSerializerBehavior(OperationDescription operation)
        : base(operation)
    {
    }

    public CustomDataContractSerializerBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
        : base(operation, dataContractFormatAttribute)
    {
    }

    public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns,
        IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, knownTypes, Int32.MaxValue, false, true, new CustomDataContractSurrogate());
    }

    public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name,
        XmlDictionaryString ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, knownTypes, Int32.MaxValue, false, true, new CustomDataContractSurrogate());
    }

}

<最后,您可以使用此行为:

public static IMyDataServiceContract CreateService()
{
    var factory = new ChannelFactory<IMyDataServiceContract>("MyServiceName");
    SetDataContractSerializerBehavior(factory.Endpoint.Contract);
    return factory.CreateChannel();
}

private static void SetDataContractSerializerBehavior(ContractDescription contractDescription)
{
    foreach (OperationDescription operation in contractDescription.Operations)
    {
        ReplaceDataContractSerializerOperationBehavior(operation);
    }
}

private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
{
    DataContractSerializerOperationBehavior dcsOperationBehavior =
    description.Behaviors.Find<DataContractSerializerOperationBehavior>();

    if (dcsOperationBehavior != null)
    {
        description.Behaviors.Remove(dcsOperationBehavior);
        description.Behaviors.Add(new CustomDataContractSerializerBehavior(description));
    }
}

要完成工作,请在某处调用上述 CreateService 来创建通道。

You can solve this using a DataContractSurrogate for the deserialization, that replaces IList with List.

public class CustomDataContractSurrogate : IDataContractSurrogate
{
    // The only function you should care about here. The rest don't do anything, just default behavior.
    public Type GetDataContractType(Type type)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(ICollection<>)))
        {
            return (typeof(List<>).MakeGenericType(type.GetGenericArguments().Single()));
        }
        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        return obj;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        return obj;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return typeDeclaration;
    }
}

Basically that's it, you just need to create your DataContractSerializer instance with that surrogate and use it for deserialization (for serialization it won't matter), for example:

var serializer = new DataContractSerializer(type, new Type[]{}, Int32.MaxValue, false, true, new CustomDataContractSurrogate());

Or any of the other constructors that take a surrogate.

Or, (as a bonus to the answer) if you're working with app/web.config-defined services, you can define a custom behavior that creates a data contract serializer with the above surrogate:

public class CustomDataContractSerializerBehavior : DataContractSerializerOperationBehavior
{
    public CustomDataContractSerializerBehavior(OperationDescription operation)
        : base(operation)
    {
    }

    public CustomDataContractSerializerBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
        : base(operation, dataContractFormatAttribute)
    {
    }

    public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns,
        IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, knownTypes, Int32.MaxValue, false, true, new CustomDataContractSurrogate());
    }

    public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name,
        XmlDictionaryString ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, knownTypes, Int32.MaxValue, false, true, new CustomDataContractSurrogate());
    }

}

Finally you can use this behavior:

public static IMyDataServiceContract CreateService()
{
    var factory = new ChannelFactory<IMyDataServiceContract>("MyServiceName");
    SetDataContractSerializerBehavior(factory.Endpoint.Contract);
    return factory.CreateChannel();
}

private static void SetDataContractSerializerBehavior(ContractDescription contractDescription)
{
    foreach (OperationDescription operation in contractDescription.Operations)
    {
        ReplaceDataContractSerializerOperationBehavior(operation);
    }
}

private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
{
    DataContractSerializerOperationBehavior dcsOperationBehavior =
    description.Behaviors.Find<DataContractSerializerOperationBehavior>();

    if (dcsOperationBehavior != null)
    {
        description.Behaviors.Remove(dcsOperationBehavior);
        description.Behaviors.Add(new CustomDataContractSerializerBehavior(description));
    }
}

To finish the job, call the above CreateService somewhere to create the channel.

新雨望断虹 2024-09-09 00:20:35

如果您使用 NetDataContractSerializer ,它将类型信息与序列化对象一起存储,您的问题应该得到解决。但是,它同时降低了与非 .NET 客户端的互操作性。

If you use the NetDataContractSerializer, which stores type information along with the serialized object, your problem should be solved. However, it does at the same time reduce interoperability to non-.NET clients.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文