[Journal - Open Instance Property Delegates and Generic Comparers]

Open Instance Property Delegates and Generic Comparers

Saturday, April 30, 2005

Sure you know the following situation:

You can be lazy and use the Gregor.Core.Collection.CReflectingComparer class, which takes an arbitrary number of property names, which are evaluated with reflection, and assumed to return an IComparable type.

But you'd think that with generics, we could have a little more type safety. Indeed, you can implement IComparer<T> or subclass Comparer<T>, and implement a dynamic multi-level comparison like I have done in CReflectingComparer.

But how do you evaluate the properties efficiently? How do you make sure that the properties' return types are assignable to IComparable? What you want to write is:

list.Sort(New FlexiComparer(Of Person)( _
                            PropertyOf(Person.Name), _
                            PropertyOf(Person.Profession)))

The imaginary FlexiComparer takes a parameter array of whatever in its constructor, whatever being "something that can be safely and efficiently used to obtain the values of the given properties", for the purposes of the article.

Of course, there are no property delegates in .NET. And "PropertyOf" is just a silly figment of my imagination.

As discussed in said article, you can fake property delegates with regular delegates pointing to the underlying getter methods. These delegates must be covariant, that is, their return type should be IComparable, and the property must return a reference type that implements this interface. Heck, you can even instantiate these delegates with reflection, as with the CreatePropertyDelegate routine of Gregor.Core.Reflect - you'd need to create the delegates only once per sorting, don't you?

Wrong. Delegates that point to instance methods are bound to an object. And we're sorting a list here, so we can't just evaluate the property on one object.

One solution is to use a callback that takes the target parameter (ie., an element in the list), handily implemented as an anonymous C# method:

delegate IComparable PropertyGetter<T>(T obj);

// ..

list.Sort(new FlexiComparer<Person>(
    delegate(Person p){return p.Name;},
    delegate(Person p){return p.Profession;}
));

But can we do better? Can we have the same level of type confidence and performance with an even shorter syntax?

I haven't found a better way, but I'm thinking of what is called "Open Instance Delegates", in which you provide the target object in the call, while using the same delegate object. It's supposed to be in Whidbey, although I haven't quite found out how to use it yet.

Summing up, we need:

  1. Open Instance delegates.
  2. Covariant delegates.
  3. Property delegates.

Open Instance Covariant Property Delegates. Property delegates can be faked with regular method delegates, and covariance is available in Whidbey, officially even.

Here's the hacked version of open instance delegates, which even works in .NET 1.0/1.1. Downside: it's not type safe.

public static void InvokeOnTarget(Delegate handler,
                                  object target,
                                  params object[] args){

    // standard checks
    Check.Reference(handler);
    Check.Reference(target);

    // special checks: no statics, method declared in target type family
    Delegate[] dels = handler.GetInvocationList();
    foreach(Delegate del in dels){
        Check.Not(del.Method.IsStatic);
        Check.Assignability(target, del.Method.DeclaringType);
        // it's OK to have different original targets
    }

    // invoke one by one on new target
    foreach(Delegate del in dels){
        try{
            del.Method.Invoke(target, args);
        }catch(Exception ex){
            Dev.ProcessException(ex, true);
        }
    }
}

So I haven't solved problem yet. Stay tuned for updates on this topic.