Why doesnt the .Net framework use Guard class (or equivalent) for method arguments
Adding to Matthews answer:
Your proposed syntax of Guard.IsNotNull(source);
is not directly equivalent to the first code snippet. It only passes the value of the parameter but not its name, so the thrown exception can't report the name of the offending parameter. It just knows that one of the parameters is null
.
You could use expression trees - like so: Guard.IsNotNull(() => source);
- but analyzing this expression tree has a rather large performance impact at runtime, so this isn't an option either.
Your proposed syntax could only be used in conjunction with a static weaver. That's basically a post-compiler that changes the generated IL. That's the approach Code Contracts are using. But this comes with its own cost, namely:
- That static weaver needs to be written by someone in the first place
- It increases build time
- The weaver also needs to patch the debug symbols
- It causes all sorts of problems with Edit and Continue
Nowadays we can do this with Code Contracts
so we can use:
Contract.Requires(source != null);
Contract.Ensures(Contract.Result<MyType>() != null);
and so on, but currently we can only do this in our own code because this isn't built in to the CLR yet (it's a separate download).
The Code Contracts classes themselves have been a part of .Net since version 4, but by themselves they don't generate any checking code. To do that we need the code contracts rewriter which will be called by the C# compiler when generating your code. That's the thing that needs a separate download.
So yes we have better ways to do this now, but it hasn't been released as part of the CLR (yet) and so the CLR is currently using what you think of as a "legacy" approach.
It's certainly nothing to do with "overloading the stack with function pointers".
Even with Code Contracts, we are still doing a check of course. There's no IL command that I know of that checks an argument for null and throws if it is, so such work has to be done using several IL instructions (in all CLR languages). However, the Code Contracts code rewriter does generate inline code to check the Code Contract's predicate (e.g. value != null
) rather than calling a method to do so, so it is very efficient.
There's no Guard class in the .NET framework so your proposed alternative is not feasible. Later additions to the framework do use code contracts, but rather sparingly. Not every .NET programmer at Microsoft seems that convinced that contracts are that useful, I do share the sentiment.
You are otherwise seeing the way Microsoft works. Code in the .NET framework is contributed by lots of small teams within the company. A typical team size is about 10 programmers. Otherwise a nod to what everybody in software dev business knows, large teams don't work. There's a critical mass where the amount of time spent on getting everybody to communicate starts to overwhelm the amount of time that can be spent on actually getting code produced.
Such teams are also constantly created and disbanded. Lots of parts of the framework no longer have an active team that maintains it. Typically just one guy that still knows the internals well enough to provide critical security updates and, maybe, bug fixes when necessary. The code that such a disbanded team wrote is very much in maintenance mode, changes are only made when absolutely necessary. Not just because there's no benefit to making minor stylistic changes but to reduce the odds that breaking changes are unknowingly added.
Which is a liability for the .NET framework, there are plenty of internals that have a knack for becoming externally visible, even if that code lives inside private methods. Like exceptions. And programmers using Reflection to hack around framework limitations. And the really subtle stuff, a great example is the bug in an email app widely used inside Microsoft, written by an intern. Which crashed and left everybody without email when they updated their machine from .NET 1.1 to .NET 2.0. The bug in that email app was a latent threading race that never triggered when running with .NET 1.1. But became visible by a very slight change in the timing of .NET 2.0 framework code.