Running with Code Like with scissors, only more dangerous


Worries over the Future of the .NET Framework

Over the last few years, as exciting as some of the things have been that have come out that make a developer’s time more and more powerful, particularly in the .NET space – LINQ, dynamic programming, lambdas – I’ve become a bit concerned over the evolution of the .NET Framework’s type system.  It seems to me like some things that should be obvious but are not are being left aside.  This post will explore some of the programming issues that I have seen and am predicting.

Automatic Type Conversion for Delegates

Consider a Predicate<T>.  A Predicate<T> delegate accepts a single parameter of type T and returns a Boolean value, true or false, indicating whether that item matched some condition.

List<int> numbers = new List<int> { 1, 2, 5, -20, 10, -5, -3, 0 };
Predicate<int> isPositive = n => n > 0;
List<int> positives = numbers.FindAll(isPositive);

In the code here, I’ve used a lambda expression to indicate that the method generated for the lambda expression should be assignable to a type of Predicate<int>.  Fortunately, C# doesn’t allow you to assign a lambda to an implicit variable (a var), but consider that I could have also written:

List<int> positives = numbers.Where(n => n > 0).ToList();

Note how the lambda expression is exactly the same, and the two pieces of code would produce identical auto-generated methods, yet I couldn’t use numbers.Where(isPositive) because Where takes a Func<T, bool>, and not a Predicate<T>.

So what’s wrong with this?  Hopefully, you can see that it would be reasonable to say that a Func<T, bool> is a Predicate<T>, and that a Predicate<T> is a Func<T, bool>.  That is, delegates in this way have a dual-direction inheritance relationship.

It gets even more interesting when you bring Actions into this.  Actions are delegates that perform some work, accepting 0 or more parameters, but have no return value.  That is, they return void.  Because C# does not allow “Void” to be a type parameter, it is impossible to create an Action<Void>, or an Action<int, Void>.  But, technically speaking, you could have such a construction.

Think about what a delegate really does.  A delegate is the information the compiler needs in order to:

  1. Push all of the parameters onto the stack in the correct order.
  2. Let the callee (the method being called) do its work.
  3. The callee cleans up its own parameters from the stack.
  4. The callee pushes its return value onto the stack, if there is one.
  5. The calling method knows, based on the signature, whether there is a return value on the stack, so it knows the state of the stack.

An Action<T> is really just a specialization of Func<T, Void>.  The two ought to be compatible, and we should stop having so many delegate types polluting the framework and Base Class Library.

Making this Work without Modifying the CLR

Delegate types are defined by IL as types as well.  It may be possible to shoehorn this feature in without modifying the CLR by:

  • Add cast overloads for all delegates to implicitly cast to a Func type that supports its method signature.
  • Add cast overloads for all delegates to be implicitly cast-to from a Func type that supports its method signature.

If I could define something like this in C#, it might look like this:

public delegate bool Predicate<T>(T item)
    public static operator implicit Func<T, bool>(Predicate<T> original)
        Func<T, bool> result = _item => original(_item);
        return result;

    public static operator implicit Predicate<T>(Func<T, bool> original)
        Predicate<T> result = _item => original(_item);
        return result;

Note that this method uses closures, which would preserve all existing object relationships and not break any object relationship semantics normally required by delegate types.

Numeric Interface for All Primitive Types, Integer Interface for Integral Primitives

Primitive types are the basic structures supported by the CLR:

  • byte/sbyte
  • short/ushort
  • int/uint
  • long/ulong
  • float / double
  • char

String is also included, but that’s a class and heap-allocated, so it’s not really important for the purposes of this discussion.

There are a lot of common operations that you can do with these types.  You can always add them, subtract them, divide them (dividing a char is a weird thing to consider, but is possible).  Actually, if you want to know the position of a given letter in the alphabet, you can just subtract ‘A’ or ‘a’ from it. 

But when it comes to something like a generic type parameter, you can’t say something like:

public class NumberAggregator<T> 
    where T : INumeric
    private T _sum = default(T);
    private List<T> _numbers = new List<T>();
    public void Add(T item) 
        _sum += item;
    // rest of the class

This is not possible.  First, there’s no such interface as INumeric, but more importantly, even though the intent of this class is to be able to sum any single size of number, the restriction can be supported by the underlying IL code.  The IL add instruction is a single instruction that does not take into regard the operands (see CIL Instruction Reference, ECMA-335 Partition III page 33). 

You can do a lot of cool things with numbers, particularly integers, that you can’t do with normal types.  You can add, subtract, divide, multiple, bitwise and/or/xor, shift, and invert (I think that covers all of them).  But the generic type system is too limited to allow you to do that.

Type Parameter Restrictions

A long while ago, I talked about the limitation of generics in that they may not support certain types as type parameter constraints, in that article specifically, enumeration types.  Enumeration types would give you similar benefits that I noted in the previous section, about the operations you can perform on numeric types that you generally can’t perform on other types.

While generics in .NET lack some of the power of C++ templates (namely, the ability to create a template that accepts a value, not just a type, as a parameter), they’re still quite good, and they solved a big performance problem in the 1.x CLR of boxing and unboxing primitive types.  However, I still believe that the CLR is limited in a way it does not need to be.  I don’t want to repeat myself, but this particular component is a big one – and a lot of the problems that issue causes could be solved with #2 above.

The .NET Library is Not Unified

Product owners who develop the core CLR need to come together and bridge the gap between incompatible interfaces.  The ISynchronizeInvoke interface was developed and part of the CLR version 1.0.  It was known as the standard way to allow cross-thread communication, most commonly for instances in which a background thread needed to post updates to the main UI thread of a Windows Forms application.

When WPF was released as part of .NET 3.0, it included a completely new model, called Dispatcher, that achieved the exact same purpose.  There was only one problem: Dispatcher doesn’t implement ISynchronizeInvoke, which means that any libraries written for previous versions of .NET, if they had code that needed to synchronize with a user interface, had an incompatible interface.

Writing an adapter for this is not an issue; making a component written for ISynchronizeInvoke work for Dispatcher is fairly trivial.  But this isn’t the only situation in which it’s happened; you have it happening with LINQ to SQL and Entity Framework (a travesty that LINQ to SQL is as closed as it is, because it could have been Entity Framework without all of the enterprisey things that EF has).  You have it within ASP.NET.  In WCF.  How about Silverlight being a reproduction of but completely incompatible implementation of WPF?

Final Thoughts

I still love programming on .NET.  It allows me to spin up applications so quickly and conveniently that I don’t imagine that I’d like to change any time soon.  But on the other hand, sometimes the decisions that the language and platform designers make baffle me. 

So – to my friends who got this far hoping that I’m making my announcement of switching to PHP, Java, or Python – sorry.  You’ll have to wait for another day.  But, hey, Mads, Anders, if we could get some of these changes in – that’d be great!

Comments (1) Trackbacks (0)
  1. Just wondering if you are over anxious about the things that looked pretty OK to me.

Leave a comment

ERROR: si-captcha.php plugin says GD image support not detected in PHP!

Contact your web host and ask them why GD image support is not enabled for PHP.

ERROR: si-captcha.php plugin says imagepng function not detected in PHP!

Contact your web host and ask them why imagepng function is not enabled for PHP.

No trackbacks yet.