27

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...

Servy
  • 202,030
  • 26
  • 332
  • 449
cholewa1992
  • 863
  • 1
  • 7
  • 16
  • 3
    `p` above is not an expression tree, it is just an anonymous function. It is compiled to a delegate that "points" to a generated method `static bool SomeFunnyName(ICollection entities) { return entities.OfType().Count() <= 3; }`. – Jeppe Stig Nielsen Apr 23 '14 at 19:26
  • In a couple of weeks you can develop your own framework to do this. – Only a Curious Mind Apr 23 '14 at 19:33
  • 5
    It's impossible to solve in the general case; not every conceivable method is going to be valid in the context in which you deserialize it, and there are plenty of things one could put into a method that inherently can't be serialized. – Servy Apr 23 '14 at 19:47
  • 1
    It's not clear exactly what you want to do - do you want to be given an expression and store it, then restore later, or would it be sufficient to maintain your expressions as literal strings? If the latter is true, you could always just compile the expression into a new assembly on-the-fly. – Simon MᶜKenzie Apr 24 '14 at 00:20
  • Why not serializing names of properties and methods to strings and then resolve them using reflection ? – aybe Apr 24 '14 at 00:20
  • 3
    If you've found a solution that you like post it as an answer; don't edit the question. – Servy Apr 16 '15 at 16:53

3 Answers3

17

My Solution:

After putting the issue to rest for a long time I 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!

cholewa1992
  • 863
  • 1
  • 7
  • 16
  • 6
    This framework seems to be dead (last update 4 years ago). A more up-to-date alternative I found, for those happening across this thread: https://github.com/esskar/Serialize.Linq – ultra909 Dec 05 '17 at 15:30
  • @ultra909 AFAIK C# hasn't changed anything to expressions, though. That's why you can't use `?.` in them. So it shouldn't matter that the code is unmaintained. – John Nov 10 '18 at 22:54
  • 2
    Please note that `Serialize.Linq` mentioned by @ultra909 is LGPL-3.0 and no MIT license. To me, this makes quite a differerence and is no direct alternative for me. – sibbl Jan 12 '21 at 08:38
  • `Serialize.Linq` MIT licensed at the time of this comment. – Hakan Deryal Jun 25 '23 at 12:42
9

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().

Josh Wyant
  • 1,177
  • 1
  • 8
  • 13
  • 3
    Seems like an awful lot of work. I think one is better of with a 'known' and tested open source framework than building one by themselves. Never the less, some insight in how it can be done is always useful. – Mike de Klerk Mar 29 '17 at 12:11
4

Remote.Linq might be the framework of choice, especially if not only dealing with simple predicates but more advanced queries including projections etc.

ChristofSenn
  • 337
  • 2
  • 6