如何在 WCF 中使用 DataContract/DataMember 序列化 Func<int, int>(甚至是一般委托)类型字段

Posted

技术标签:

【中文标题】如何在 WCF 中使用 DataContract/DataMember 序列化 Func<int, int>(甚至是一般委托)类型字段【英文标题】:How to serialize a Func<int, int> (or even a general delegate) type field with DataContract/DataMember in WCF 【发布时间】:2015-07-25 21:24:20 【问题描述】:

我正在尝试序列化标记为[DataMember] 的类的Func&lt;int, int&gt; 字段,该字段标记为[DataContract],其目标或 Lambda 表达式定义也是合同的一部分,甚至可能在类本身中。

我正在使用自定义DataContractResolver,当我取消标记Func 字段时效果很好,但是当我标记它时,它不能解析序列化所需的System.DelegateSerializationHolder 类型。我尝试将此类型添加到我的自定义 DataContractResolver,但在 System 命名空间中找不到它。

我可以在使用此类的每个客户端类的 [OnDeserialized] 方法中重新定义它,但是每次使用此类时我都需要这样做,当然我想避免这种情况(客户端类也是一部分DataContract)。

此序列化暂时用于将应用程序的状态保存到磁盘,因此保存委托(甚至是我不感兴趣的事件)具有逻辑意义,因为它是对象图的一部分。

那么如何使用 WCF 的 DataContract 序列化这个 Func&lt;int, int&gt;

【问题讨论】:

如果你序列化一个委托,你是否也想序列化委托附加到的实例? 正如我所说,所有涉及的类都是 DataContract 的一部分,并且是序列化的一部分。 是的......但“参与”并不意味着“序列化”,“类”与“实例”不同。这就是我要求澄清的原因。 Func 字段属于一个正在序列化的类。它的定义(通常是一个 Lambda 表达式)也在一个属于被序列化的总对象图的类中。使用所有这些的类也是同一个对象图的一部分。所以它基本上是一个封闭的系统,可以序列化维护所有对象引用。 二进制序列化是否适合您? 【参考方案1】:

问题在于无法为此使用默认的 WCF 序列化程序 - 委托序列化仅适用于 .NET。

解决方案是改用NetDataContractSerializer。在您的情况下,使用持久性上下文(或 File 上下文):

var context = new StreamingContext(StreamingContextStates.Persistence);
var s = new System.Runtime.Serialization.NetDataContractSerializer();

var sb = new StringBuilder();

using (var writer = new XmlTextWriter(new StringWriter(sb)))
      
    s.WriteObject(writer, new Test()  SomeFunc = (int i) => "Hi".Dump().Length );


sb.ToString().Dump();

[DataContract]
class Test

    [DataMember]
    public Func<int, int> SomeFunc;

请注意,序列化程序只会维护有关委托的信息 - 如果您只为您的委托使用编译时方法,那很好,但它不会 > 为动态编译的方法工作。请注意。

避免匿名函数也是一个好主意 - 虽然它们工作,但它们也可能在不同的构建之间发生变化,导致您的持久状态变得无效,可能带来灾难性的副作用。不要忘记,委托是通过方法+类名(以及返回类型和参数...)进行序列化的——当名称更改时,持久化的委托将指向具有旧名称的新方法。即使使用非匿名方法,也要尽量确保您永远不会更改可能作为委托持久存在的方法 - 理想情况下,如有必要,使用原始方法签名提供向后兼容的行为。

如果您发现自己添加了对网络序列化的支持,只需添加更多 StreamingContextStates

【讨论】:

“动态编译的方法”是什么意思。在我的情况下,直到运行时我才知道实际的Func&lt;int, int&gt;,当它根据一些运行时参数由方法获取时。 @GDS 没关系。问题是如果该方法根本不存在于编译时代码中 - 例如,如果它是在运行时编译表达式树(例如LambdaExpression)的结果。如果确实涉及运行时生成的代码,则必须添加一两个额外的层来确定性地处理它。【参考方案2】:

我知道这篇文章已有几年历史了。我找到了解决这个问题的方法,更多的是我使用了 mikecodes.net 知识。因此,请查看他的网站以获取更多信息。这是我的简化:

using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using System;

public static class Script

    public static Func<int, int> Compile(string code) => CSharpScript.EvaluateAsync<Func<int, int>>(code, ScriptOptions.Default).Result;

[Serializable] // DataContract stuff
public class Data

    public string Code  get; set; 
    public string Name  get; set; 
    public int Value  get; set; 

    private Func<int, int> _func = null;
    public Func<int,int> Function
    
        get
        
            if(_func ==null)
            
                _func = Script.Compile(this.Code);
            
            return _func;
        
    


public class Program

    public void Main()
    
        var data = new Data
        
            Code = "i=>i + 1",
            Name = "Sample",
            Value = 2
        ;
        Console.WriteLine($"Function = data.Function(data.Value)");
    

【讨论】:

以上是关于如何在 WCF 中使用 DataContract/DataMember 序列化 Func<int, int>(甚至是一般委托)类型字段的主要内容,如果未能解决你的问题,请参考以下文章

如何在数据合同 WCF 中使用枚举

在 WCF 中命名通用 DataContract

是否可以在 WCF 服务的 DataContract 中定义位的使用?

如何拥有一个带有 json 动态成员的 WCF DataContract

WCF:MessageContract、DataContract ... 感到困惑?

如何序列化/反序列化 C# WCF DataContract 到 XML