LinqPossibilities

time to read 22 min | 4320 words

Jeff Brown commented on my Linq Options post:

Pity they didn't shoot for lexically-scoped blocks a la SmallTalk (or Ruby)...  This approach with Expressions has the same control-flow limitations as anonymous delegates but you can omit the curly braces sometimes.

Linq is not just anonymous delegates. (I should be clear that I am mostly thinking about the abilities of Expression rather than the Langague Integrated Query here). It means that I can start doing some really stuff. For what it worth, there is such a thing as the ExecutionScope for linq, but I am not sure what it is supposed to do, as far as I can see, it is the entire lexical scope for the expression.

Here is a trivial example that shows what you can do with it. Assume that I have this work item (and saved action):

[Serializable]
public class WorkItem

{

    string name;

    string action;

    string on;

 

    public WorkItem(string name, string action, string on)

    {

        this.name = name;

        this.action = action;

        this.on = on;

    }

    public WorkItem() {}

 

    public void DoAction()

    {

        Console.WriteLine(name +" "+action+" " +on);

    }

}

 

[Serializable]
private class SavedAction
{
   public object target;
   public string method;
}

And I have this code:

public delegate void Act();

static void Main(string[] args)
{
    WorkItem wi = new WorkItem("Ayende,", "write", "blog post");
    Save("Temp.action",() => wi.DoAction());
    Act act = Load("Temp.action");
    act();
}

What is going on here? I am saving the labmda into a file in the Save(), then load and execute it in the next two statement. Sadly, Linq's Expression<T> are not serializable, which I consider a huge minus, but for this example, I worked around it a bit. Here is the code for the Load, which isn't really interesting:

private static Act Load(string file)
{
    SavedAction action;
    BinaryFormatter bf = new BinaryFormatter();
    using (Stream s = File.OpenRead(file))
        action = (SavedAction)bf.Deserialize(s);
    return (Act)Delegate.CreateDelegate(typeof(Act), action.target,
        action.target.GetType().GetMethod(action.method));
}

The Save() is where the real magic begins, I compile the expression, extract the target, extract the method that was about to call, and save it, for later processing in the load.

private static void Save(Expression<Act> actionToSave)
{
    Act act = actionToSave.Compile();
    ExecutionScope scope = (ExecutionScope)act.Target;
    SavedAction action = new SavedAction();
    MethodCallExpression l = (MethodCallExpression)actionToSave.Body;
    action.target = Expression.Lambda(l.Object).Compile().DynamicInvoke();
    action.method = l.Method.Name;
    BinaryFormatter bf = new BinaryFormatter();
    using(Stream s = File.Create("Temp.action")) 
        bf.Serialize(s, action);
}

I am very excited about these capabilities. Yes, I can do it today, but the inteface I would have to expose is wholly unatural, while Linq provide for much nicer alternative.

More posts in "Linq" series:

  1. (14 Apr 2007) Functions
  2. (05 Apr 2007) More Implementation Details
  3. (20 Mar 2007) Orderring and Paging