共用方式為


Sorting on Dynamic Members at Runtime

Earlier this year I got to play with the DLR a little while working on the WebGrid for ASP.NET Web Pages.  I wanted the grid to work with the various LINQ providers, but it also had to support dynamic objects such as those returned by the new simple data APIs.  This began my investigation into using LINQ with dynamics.

With non-dynamic objects, you can order on LINQ’s Expression.Property to reflect against an arbitrary property at runtime:

 static void Main(string[] args) {
    var names = new[] {
        new { Name = "Joe" },
        new { Name = "Bob" },
        new { Name = "Tom" },
        new { Name = "Jack" },
    };
    Print(names);
    Print(Sort(names, "Name"));
}

static void Print(IEnumerable<dynamic> source) {
    foreach (var i in source.Select(s => s.Name)) Console.WriteLine(i);
    Console.WriteLine();
}

public static T[] Sort<T>(T[] source, string property) {
    var param = Expression.Parameter(typeof(T));
    var getter = Expression.Property(param, property);
    var lambda = Expression.Lambda<Func<T, dynamic>>(getter, param).Compile();
    return source.OrderBy(lambda).ToArray();
}

Of course, this doesn’t work for dynamic objects.  You need to sort on DynamicObject.TryGetMember with a get member binder that can invoke the property.  My first inclination was to write a helper method for getting the value with a binder, and using Expression.Call to invoke it:

 class DynamicPerson : DynamicObject {
    private string _name;
    public DynamicPerson(string name) {
        _name = name;
    }
    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        if (binder.Name == "Name") {
            result = _name;
            return true;
        }
        return base.TryGetMember(binder, out result);
    }
}

class MyBinder : GetMemberBinder {
    public MyBinder(string property) : base(property, ignoreCase:true) { }
    public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) {
        throw new NotImplementedException();
    }
}


static void Main(string[] args) {
    var names = new dynamic[] {
        new DynamicPerson("Joe"),
        new DynamicPerson("Bob"),
        new DynamicPerson("Tom"),
        new DynamicPerson("Jack")
    };
    Print(names);
    Print(Sort(names, "Name"));
}

static DynamicObject[] Sort(IEnumerable source, string property) {
    var param = Expression.Parameter(typeof(DynamicObject), "o");
    var param2 = Expression.Constant(property);
    var getter = Expression.Call(typeof(Example2), "GetDynamicMember", new Type[0], param, param2);
    var lambda = Expression.Lambda<Func<DynamicObject, dynamic>>(getter, param).Compile();
    return source.Cast<DynamicObject>().OrderBy(lambda).ToArray();
}

static object GetDynamicMember(DynamicObject o, string property) {
    object result = null;
    if (o.TryGetMember(new MyBinder(property), out result)) {
        return result;
    }
    return null;
}

This looks ugly and also doesn’t work in partial trust since the lambda executed by LINQ is using your internals.  Fortunately there’s a simple fix: Expression.Dynamic.  Using this, LINQ can bind to the dynamic property using the runtime binder.

 using Microsoft.CSharp.RuntimeBinder;
 static DynamicObject[] Sort(IEnumerable source, string property) {
    var binder = Binder.GetMember(CSharpBinderFlags.None, property, typeof(Example3), new CSharpArgumentInfo[] {
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
    var param = Expression.Parameter(typeof(DynamicObject), "o");
    var getter = Expression.Dynamic(binder, typeof(object), param);
    var lambda = Expression.Lambda<Func<DynamicObject, dynamic>>(getter, param).Compile();
    return source.Cast<DynamicObject>().OrderBy(lambda).ToArray();
}

UPDATE: There are some considerations you should take into account before extending DynamicObject