Saturday, January 19, 2008

Consolidating Delegates with Reflector

Have you ever wondered whether there is an existing delegate somewhere in the BCL that meets your needs? Ever wish you could search by delegate signature and find any matching delegate? Me too. After all, why create yet another type when an existing type will suffice? This is especially true when you're not using the delegate as any part of your public API.

One way I've found to do this is by using the Code Search add in for Reflector. The Code Search add in allows you to search through disassembled code for matches to a regular expression that you supply. It has some limitations that we have to work around, but we can still get the job done.

Firstly, it only searches from the currently selected node in the left-hand tree view and down. Since there is no root node, you can only search one assembly at a time, which is a minor inconvenience. For example:

image

Since mscorlib is selected in the tree view, it is the only assembly that will be searched. Typically you will want to search for delegates at least in mscorlib and System, and sometimes you will want to search other assemblies that your project references. Therefore, you will need to repeat the search a number of times.

The second - and more annoying - limitation is that type declarations are not searched, only the members in types are. This means we can't search for a delegate declaration like "public delegate ..." but instead have to find something common to the implementation of all delegates that we can use to identify them. Fortunately, all delegates will have a method of form:

public virtual ReturnType Invoke(Parameters);

Therefore we have a handle with which we can find delegates. However, we will get some false positives, such as finding non-public delegates and finding methods called Invoke() that aren't part of a delegate type (we could probably work around that, but it wouldn't be worth the effort). That said, it's simple to identify and ignore these false positives.

Suppose we want to find an existing delegate with this signature:

public delegate void DelegateName(object argName);

We don't care what the delegate is called, nor what the argument is called. All we want is a delegate that takes a single argument of type object and returns void. We can find all matching delegates with this regular expression:

public virtual void Invoke\(object [^,]*?\);

Here are the results from running this query against mscorlib:

image

There are a couple of false positives (CtorDelegate and DeserializationEventHandler are both internal) but we can see that there are a bunch of delegates we could use to satisfy our requirements.

Now what about a delegate matching this signature:

public delegate bool DelegateName(Type argName1, object argName2);

We can use an expression of public virtual bool Invoke\(Type [^,]*?, object [^,]*?\); to find a suitable delegate called TypeFilter:

image

Finding generic delegates becomes problematic:

public delegate void DelegateName<T>(object argName1, T argName2);

To match the above delegate, we need to find a method like this:

public virtual void Invoke(object argName1, T argName2);

Problem is, we don't care whether the delegate declared the generic type as T, or something else (like TEventArgs in the EventHandler<TEventArgs> delegate). So the best we can do is search for a two argument Invoke() method whose first parameter is of type object.

One final tip: we can find all delegates in a given assembly by using the expression public virtual \S*? Invoke\(.*?\); as pictured below (again, against mscorlib):

image

kick it on DotNetKicks.com

5 comments:

zproxy said...

I think Action<> and Func<> delegates work for the most scenarious :)

Kent Boogaart said...

And if you're not on V3.5? ;)

/SiD said...

Action< T > is there since 2.0 :)

Kent Boogaart said...

Yeah, but zproxy is referring to the new 3.5 delegates:

- Action<T1,T2>
- Action<T1,T2,T3>
- Action<T1,T2,T3,T4>

and

- Func<TResult>
- Func<T1,TResult>
- Func<T1,T2,TResult>
- Func<T1,T2,T3,TResult>
- Func<T1,T2,T3,T4,TResult>

Action<T> is an obvious case where you're going to know the name of the delegate that you want because you use it all the time. The point of this post is to help you find the non-obvious ones.

/SiD said...

I know that the mentioned are new in 3.5 (and also Action() for that matter)

..that's why I put the smiley there.