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.
4 comments:
Excellent, thanks for that! I wonder why methods like HashSet<T>.RemoveWhere takes a predicate when every Enumerable extension method work with Func<T...>'s. They were all added at the same time with .NET 3.5!
I've written a functor library that may help address this problem. See http://jolt.codeplex.com for more information.
Dear Sir,
I have a launched new web site for .NET programming resources. www.codegain.com. I would like to invite to the codegain.com as author and supporter. I hope you will joins with us soon.
Thank You
RRaveen
Founder www.codegain.com
Post a Comment