Serialize expression tree

2020-02-20 08:46发布

I'm doing a distributed system in c# and have encountered a barrier.

I need to be able to serialize Predicate with type

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

I belive this is not possible in .net so my question is if there exists any frameworks that can do the trick.

I've already tried a couple of frameworks, but keep running into the problem that their are not able to serialize predicates that takes a collection or list

Hope anyone knows a solution. Have been stuck with this problem for a couple of weeks now...

2条回答
神经病院院长
2楼-- · 2020-02-20 09:13

I've attempted this before. It will take some work, but you can develop your own protocol to pass predicates across a network.

First, you need to change the type of your p variable to an Expression<TDelegate> so it can be deconstructed:

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

VisitExpression(p);

The C# compiler will see that you are assigning a lambda to an Expression<TDelegate> variable, and it will actually build an expression tree for you.

Now, you can walk the expression tree and serialize it to your custom protocol. I'll use a StringBuilder here, to create a JSON object (for easy deserialization).

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("}");
}

Depending on how your distributed system handles collections and lists, you will need to implement the corresponding logic when walking the expression tree. I would start by using typeof(IEnumerable<>).MakeGenericType(typeof(IEntity)).IsAssignableFrom(typeToTest).

When serializing, you will have to send the full names of the types, methods, and overloads across the network. You'll probably want to make sure that each compute node is referencing all the same libraries, so that you can correctly resolve the types and methods when you deserialize everything.

When you finally deserialize, rebuild the expression tree on the remote host using the classes in the System.Linq.Expressions namespace. Then, compile and run the expression using Lambda.Compile().

查看更多
聊天终结者
3楼-- · 2020-02-20 09:19

My Solution:

After putting the issue to rest for a long time a finally managed to solve my problem using json.net and 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);
    }
}

Works like a charm!

查看更多
登录 后发表回答