序列化表达式树

27

我正在使用C#开发分布式系统,遇到了一个障碍。

我需要能够序列化具有类型的Predicate。

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

我相信在 .net 中这是不可能的,所以我的问题是是否存在可以解决这个问题的框架。

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

希望有人知道解决方案。我现在被这个问题卡了几个星期了...


3
p并不是一个表达式树,它只是一个匿名函数。它被编译成一个委托,指向一个生成的方法static bool SomeFunnyName(ICollection<IEntity> entities) { return entities.OfType<Person>().Count() <= 3; } - Jeppe Stig Nielsen
在几周内,您可以开发自己的框架来做到这一点。 - Only a Curious Mind
5
在一般情况下是不可能解决的;并非在反序列化时可以使用任何可能的方法,而且有许多本质上无法被序列化的东西可以放入方法中。 - Servy
1
不清楚你想要做什么 - 你是想给出一个表达式并存储它,然后再恢复,还是仅将表达式保留为字面字符串即可?如果后者成立,你可以随时在运行时将表达式编译成新的程序集。 - Simon MᶜKenzie
为什么不将属性和方法的名称序列化为字符串,然后使用反射解析它们呢? - aybe
3
如果您找到了一个喜欢的解决方案,请将其发布为答案;不要编辑问题。 - Servy
3个回答

17

我的解决方案:

经过长时间的努力,我终于使用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);
    }
}

非常好用!


6
这个框架似乎已经不再更新(最后一次更新是4年前)。我找到了一个更为现代的替代品,供那些浏览这个帖子的人参考:https://github.com/esskar/Serialize.Linq - ultra909
据我所知,C#在表达式方面没有做出任何改变。这就是为什么你不能在其中使用“?.”。因此,代码未被维护不应该成为问题。 - John
2
请注意,@ultra909提到的Serialize.Linq是LGPL-3.0许可证,而不是MIT许可证。对我来说,这是非常重要的区别,因此并不是直接的替代选择。 - sibbl
Serialize.Linq 在此评论时是以 MIT 许可证发布的。 - Hakan Deryal

9

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

首先,您需要将变量 p 的类型更改为 Expression<TDelegate>,以便对其进行分解:

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

VisitExpression(p);

C#编译器会发现你将一个lambda分配给Expression<TDelegate>变量,它实际上会为你构建一个表达式树。

现在,你可以遍历表达式树并将其序列化到你的自定义协议中。我将使用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<>).MakeGenericType(typeof(IEntity)).IsAssignableFrom(typeToTest)开始。
在序列化时,您需要通过网络发送类型、方法和重载的完整名称。您可能需要确保每个计算节点都引用了所有相同的库,以便在反序列化时正确解析类型和方法。
最后,在远程主机上使用System.Linq.Expressions命名空间中的类重建表达式树。然后,使用Lambda.Compile()编译和运行表达式。

3
似乎需要做很多工作。我认为使用一个已经“知名”和经过测试的开源框架比自己构建一个更好。尽管如此,了解如何实现总是有用的。 - Mike de Klerk

4

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接