Back in C++, I had a type that looked something like this (entering
from memory so this might not quite compile):
template <class T>
class NotNull
{
private:
T* value;
public:
NotNull(T* ptr)
:value(ptr)
{
if (ptr==0)
{
// Throw an exception here.
}
}
T& operator*()
{
return *value;
}
// ...
private:
// Private constructor prevents default initialisation:
NotNull() {}
};
There's a bit more to it that I've omitted there, but essentially it
behaves like a pointer except that it throws an exception if you try
to initialise it to null. This is really handy to use as a parameter
type. A method that takes these as parameters doesn't need to have a
bunch of null argument tests because it knows that it's simply not
possible for these to be null. (Sadly C++ references can end up null
through careless code, which is why this class is used rather than
references.) I've been trying to create some kind of analogue in C#,
because otherwise it seems necessary to test for null arguments in
virtually every method, since every single class reference could be
null.
For a start, I can't use a class for this because the reference to it
itself could be null. I've been looking at using a struct, and this
partially works. I can do this:
struct NotNull<T>
{
public T Value
{
get
{
if (value==null)
{
throw new NotNullException("An uninitialised NotNull
was accessed.");
}
return value;
}
}
public NotNull(T v)
{
if (v==null)
{
throw new NotNullException("A NotNull was initialised to
null.");
}
value = v;
}
private T value;
};
This looks like it will more-or-less work, but I don't like the fact
that the default constructor allows a null NotNull to exist in the
first place, and necessitates the null test in the get accessor. A
method that takes one of these as a parameter cannot be entirely
robust to bad usage unless it does the null argument testing that this
was supposed to avoid. E.g.:
void AddToMyCollection(NotNull<Widgetw)
{
AllocateSlot();
PutWidgetInSlot(w.Value);
}
Since the backup null test happens only when w.Value is accessed, an
exception could be thrown after the call to AllocateSlot() and
presumably leaves the collection in an invalid state. It's not
*likely*, because it would require the caller to do something like:
AddToMyCollection(NotNull());
....but I feel a bit uneasy that it's not as water-tight as the C++
solution was. Any suggestions? Is there a better way to do this?