Thursday 20 November 2008

CF woes

Grumble grumble.

I spent some time on the train this morning scratching my head wondering why some perfectly innocent code was failing in protobuf-net; a user had (quite reasonably) reported the issue, and I was investigating...

Now, I'm not usually a mobile developer, so this might be well known to CF developers - but it was news to me! Here's the setup (after taking out lots of stuff that doesn't matter) - see if you can spot the problem:

static void Foo<T>(T[] values)
{
List<Wrapper<T>> list = new List<Wrapper<T>>();
foreach(T value in values)
{
list.Add(new Wrapper<T>(value));
}
list.Sort(delegate (Wrapper<T> x, Wrapper<T> y) {
return x.SomeProp.CompareTo(y.SomeProp);
}); // BOOM!!!
// or even just list.Sort();
}

All it does is wrap the values in a wrapper, add them to a list, and sort them by a property of the wrapper (a simple, field-based property - nothing fancy). As it happens, the same thing happens even with just the regular Sort() [assuming you implement IComparable or the generic counterpart].

Looks innocent enough, yes? And it will work in every framework except CF 2.0 - so: can you predict the problem?

It turns out that the CF 2.0 security model is severely, erm.... "interesting" when combined with generics. Even though we don't attempt anything evil via reflection, the above code can throw a MethodAccessException. Well, it was news to me!

The problem is that in CF 2.0, it really, really wants T to be accessible to this method... which means that if Foo and T are in different assemblies, T must be public. Otherwise, even if we don't so much as look sideways at T, the code will fail. And if the type is nested in another type, the containing type must also be public. And so on. Does anybody else smell a fairly large runtime bug here? This pretty much defeats the whole point of "You give me a T (I don't care what), and I know what to do" - with CF 2.0 it is "You give me a T (but please, pretty please, do something that I can't enforce on a constraint), and I might maybe sometimes work without crashing".

The good news is that this is fixed in CF 3.5...

But there you go: if you want to work with generics and library dlls in CF 2.0, then you are probably going to have to make your classes public.