【问题标题】:Serialize expression tree序列化表达式树
【发布时间】:2014-06-08 19:45:43
【问题描述】:

我在用c#做一个分布式系统,遇到了一个障碍。

我需要能够用类型序列化 Predicate

Predicate<ICollection<IEntity>> p = (entities => entities.OfType<Person>().Count() <= 3);

我相信这在 .net 中是不可能的,所以我的问题是是否有任何框架可以做到这一点。

我已经尝试了几个框架,但一直遇到的问题是它们无法序列化采用集合或列表的谓词

希望有人知道解决方案。已经被这个问题困扰了几个星期了......

【问题讨论】:

  • 上面的 p 不是表达式树,它只是一个匿名函数。它被编译为“指向”生成的方法static bool SomeFunnyName(ICollection&lt;IEntity&gt; entities) { return entities.OfType&lt;Person&gt;().Count() &lt;= 3; }的委托。
  • 在几周内,您可以开发自己的框架来执行此操作。
  • 一般情况下无法解决;并非所有可以想象的方法在您反序列化它的上下文中都是有效的,并且有很多东西可以放入本质上无法序列化的方法中。
  • 不清楚你到底想做什么——你想得到一个表达式并存储它,然后再恢复,还是将你的表达式保持为文字字符串就足够了?如果后者为真,您总是可以将表达式即时编译成新的程序集。
  • 如果您找到了喜欢的解决方案,请将其发布为答案;不要编辑问题。

标签: c# serialization lambda expression-trees


【解决方案1】:

我的解决方案:

在将问题搁置了很长时间后,终于设法使用 json.net 和 Aq.ExpressionJsonSerializer (https://github.com/aquilae/expression-json-serializer) 解决了我的问题

public class JsonNetAdapter : IOconSerializer
{
    private readonly JsonSerializerSettings _settings;

    public JsonNetAdapter(JsonSerializerSettings settings = null)
    {
        var defaultSettings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Objects};
        defaultSettings.Converters.Add(new ExpressionJsonConverter(Assembly.GetAssembly(typeof(IOconSituation))));
        _settings = settings ?? defaultSettings;
    }

    public string Serialize<T>(T obj)
    {
        return JsonConvert.SerializeObject(obj, _settings);
    }

    public T Deserialize<T>(string json)
    {
        return JsonConvert.DeserializeObject<T>(json, _settings);
    }
}

像魅力一样工作!

【讨论】:

  • 这个框架似乎已经死了(最后一次更新是 4 年前)。我发现了一个更新的替代方案,对于在这个线程中发生的那些:github.com/esskar/Serialize.Linq
  • @ultra909 不过,AFAIK C# 并没有对表达式进行任何更改。这就是为什么你不能在其中使用?.。因此,代码未维护应该无关紧要。
  • 请注意@ultra909 提到的Serialize.Linq 是LGPL-3.0 并且没有MIT 许可证。对我来说,这有很大的不同,对我来说没有直接的选择。
【解决方案2】:

我以前尝试过。这需要一些工作,但您可以开发自己的协议来通过网络传递谓词。

首先,您需要将p 变量的类型更改为Expression&lt;TDelegate&gt;,以便对其进行解构:

Expression<Predicate<ICollection<IEntity>>> p = (entities => entities.OfType<Person>().Count() <= 3);

VisitExpression(p);

C# 编译器将看到您将 lambda 分配给 Expression&lt;TDelegate&gt; 变量,它实际上会为您构建一个表达式树。

现在,您可以遍历表达式树并将其序列化为您的自定义协议。我将在这里使用StringBuilder 来创建一个 JSON 对象(以便于反序列化)。

StringBuilder sb = new StringBuilder();

void VisitExpression(Expression e)
{
    switch (e.ExpressionType)
    {
    case ExpressionType.And:
        return VisitBinaryExpression(e As BinaryExpression);

    ...
    }
}

void VisitBinaryExpression(BinaryExpression e)
{
    sb.AppendLine("{");
    switch (e.ExpressionType)
    {
    case ExpressionType.And:
        sb.Append("\"Type\": \"And\",");
        break;

    ...
    }
    sb.Append("\"Left\":");
    VisitExpression(e.Left); sb.Append(",");
    sb.Append("\"Right\":");
    VisitExpression(e.Right);
    sb.AppendLine("}");
}

根据您的分布式系统如何处理集合和列表,您需要在遍历表达式树时实现相应的逻辑。我会先使用typeof(IEnumerable&lt;&gt;).MakeGenericType(typeof(IEntity)).IsAssignableFrom(typeToTest)

进行序列化时,您必须通过网络发送类型、方法和重载的全名。您可能需要确保每个计算节点都引用所有相同的库,以便在反序列化所有内容时正确解析类型和方法。

当您最终反序列化时,使用 System.Linq.Expressions 命名空间中的类在远程主机上重建表达式树。然后,使用Lambda.Compile() 编译并运行表达式。

【讨论】:

  • 看起来工作量很大。我认为使用“已知”和经过测试的开源框架比自己构建一个更好。尽管如此,对如何做到这一点的一些见解总是有用的。
【解决方案3】:

Remote.Linq 可能是首选框架,尤其是在不仅处理简单谓词而且处理更高级查询(包括投影等)时。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 2010-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-20
    • 1970-01-01
    相关资源
    最近更新 更多