System.Query
defines a number of delegate types to help with query expressions. They follow the pattern of:
delegate T Func<T>();
delegate T Func<A0, T>(A0 arg0);
delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);
// etc.
This is all well and good.
Certain methods in System.Query.Sequence
take a parameter of Func<T, bool>
. An example is Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
. Note that the underlying signatures of Func<T, bool>
and Predicate<T>
match.
Well it turns out that Delegate
s are kind of funky. For one thing, the Delegate
class cannot be used as a generic type constraint. Also, conversion between compatible types of delegates isn't allowed (that is, you can't cast them).
That means that if one has a reference to an existing Predicate<T>
one cannot use it as an argument to Where
. I'm sure it won't come up much, because it's not often that one has a reference to a delegate without being able to reach the method that the delegate referrs to, but it could happen.
The best way I came up with to convert between two types of compatible delegates:
// CS0702: Constraint cannot be special class 'System.Delegate'
public static T ConvertTo<T>(this Delegate source)
where T : class //, Delegate
{
if (source.GetInvocationList().Length > 1)
throw new ArgumentException("Cannot safely convert MulticastDelegate");
return Delegate.CreateDelegate(typeof(T), source.Target, source.Method) as T;
}
This isn't great. Something like the following compiles just fine:
Predicate<int> isEven = n => n % 2 == 0;
MailAddress email = isEven.ConvertTo<MailAddress>();
Since Delegate
is invalid as a type constraint, there's not really any way around it. I don't think that will ever cause any difficulty, though.
More subtly evil, however, is that conversion between incompatible delegates will appear to work fine at compile time, but result in a runtime error. Oh well. At least now I can do this:
int[] numbers = { 1, 2, 3, 4, 5 };
Predicate<int> isEven = n => n % 2 == 0;
foreach (var n in numbers.Where(isEven.ConvertTo<Func<int, bool>>()))
Console.WriteLine(n);
// Output:
// 2
// 4
… not that I ever would…
Update: the delegate conversion method has been extended/updated.