b-side: dot-net shortcomings

Posted by Amos Robinson on January 2nd, 2008 filed in

At work, I predominantly write C#, with sprinklings of VB.Net. I’m a bit displeased with certain things in the computer world, and dot-net is definitely a major factor at the moment.
Let’s see some interesting points…

const is crippled

Or rather, the type-system is still lacking. Consider the following C++ code:

class Farm
{
public:
	const std::vector<Animal>& getAnimals() const
	{
		return this->animals;
	}
};

void UserCode(const Farm& farm_)
{
	farm_.getAnimals()[2].getName(); // OK, getName is obviously const.

	farm_.getAnimals()[2].setName("Naughty value"); // NO! compile-time error
	farm_.getAnimals()[2] = Animal("Fish"); // NO! compile-time error
	farm_.getAnimals().push_back(Animal("Fish")); // NO! compile-time error
}

The dot-net people must have at least half realised this level of type safety was a good idea, because they have a ReadOnlyCollection<T>, but this wrapper class shies away from any real usefulness.

class Farm
{
	public ReadOnlyCollection<Animal> Animals
	{
		get { return this.animals; }
	}
}

void UserCode(Farm farm_)
{
	string name = farm_.Animals[2].Name; // OK, cool

	farm_.Animals[2].Name ="Naughty value"; // compiles fine, runs fine.

	farm_.Animals[2] = new Animal("Fish"); // doesn't compile, interestingly... however:
	(IList<Animal>)farm_.Animals[2] = new Animal("Fish"); // compiles, throws NotSupportedException

	farm_.Animals.Add(new Animal("Fish")); // compile-time error, need upcast as above
}

I don’t know what to say. What’s the point of designing a new, supposedly modern statically strongly typed language if you don’t have a decent type system?

generics vs arithmetic

I like templates. And the concept of generics isn’t too bad either. So instead of having to write

public int Sum(IEnumerable<int> xs)
{
	int sum = 0;
	foreach (int x in xs) {
		sum += x;
	}

	return sum;
}

Generics should let us write some generic code like so:

public T Sum<t>(IEnumerable<t> xs) where T: INumber
{
	T sum = default(T);
	foreach (T x in xs) {
		sum += x;
	}

	return sum;
}

(Even better would be just passing the + op to a foldl, but that’s a whole ‘nother level)
However, there is no such interface as INumber. The numeric builtins System.Int32, Int64, etc have no sort of common hierarchy. If you look at the definition of, say, Math.Min, it must be something like:
public static int Min(int a, int b) { return (a < b)? a : b; }
public static decimal Min(decimal a, decimal b) { return (a < b)? a : b; }
public static uint Min(uint a, uint b) { return (a < b)? a : b; }

And so on. For every numeric type. But when the time comes to use that non-standard ultra-fast arbitrary precision floating point library, why shouldn’t Math.Min work on it? An ideal Math.Min implementation is simply
public static T Min<t>(T a, T b) where T: INumber { return a < b)? a: b; }

But our overlords have deemed that unnecessary.

legacy code in a seven-year-old language

I guess this isn’t an exactly unique problem, but it just shows the philosophy of the language: make money, trap developers. C# was released in 2001. Generic programming has been around since the late seventies or so (Ada?), every C++ programmer uses it, but Java and C#, I can only assume, were in a rush to start making money and wrote languages with crippled type-systems, only to have to come back and try their best to fix them later. (Sadly, so far it looks like it’s turned out to be a good move for them.)

IList list = GetList();
int is_it_an_int = (int)list[0];
string is_it_a_string = (string)list[0];
Animal is_it_an_animal = (Animal)list[0];

Statically, strongly and safely typed piece of crap. Then five years later they decide to fix it, sure, but we’re still forced to put up with legacy code that’s not even typesafe.

delegates and events are weird

Consider:
event MyDelegate foo;
foo(...); // throws NullRef because foo is null. however:
foo += ...; // we can add a delegate to it fine!
foo(...) // and op+= method magically turns the lhs into an instance!

That’s not normal or expected. The only special thing about events to stop them being implemented in user-code is that their operator() takes strongly typed varargs (e.g. object source, WindowEvent event), but some generics-fu should be able to cope with that. Except that generics didn’t exist when events were written, but they were happy enough with un-type-safe Lists, so what’s so bad about un-type-safe events?

floating point equality

Spot the error:
float x = 1.0f / 65536.0f;
float y = 1.0f / 65536.0f;
if (x == y) { /* ow! potentially not called! */ }

Hmm. Even though x and y use the same calculation, apparently something can happen along the lines of:

  • x is calculated, stored in mem and register
  • y is calculated, stored in mem and overwrites same register
  • the x == y uses the mem value of x, and the register value of y

But the problem there is that an x86 floating point register is 80-bits, whereas a float is 32-bits. So x loses accuracy, and is compared bitwise against the original. Uh oh.

VB.Net disallows empty structs

This is just weird. Try this C# out:
public struct Empty
{
}

That compiles fine, as you’d expect. Not very useful, but it’s still fine. The VB.Net equivalent, though:
Structure Empty
End Structure

Is a compile-time error. The empty class works fine in both though… Strange.

And here’s an interesting article I just came across: comments considered evil

Leave a Comment