This small utility class is my solution for a more convenient way for checking against null. I also wanted to have more informative NullReferenceException
s but I didn't want to pollute my code with null checks everywhere.
static class NullGuard
{
public static T Check<T>(Expression<Func<T>> expr) where T : class
{
T value = expr.Compile()();
if (value == null)
{
var message = CreateMessage(expr.Body);
throw new NullReferenceException(message);
}
return value;
}
private static string CreateMessage(Expression expr)
{
return CreateMessage(expr as MemberExpression) ?? CreateMessage(expr as MethodCallExpression);
}
private static string CreateMessage(MemberExpression expr)
{
if (expr == null)
{
return null;
}
string instanceName = null;
var constantExpression = expr.Expression as ConstantExpression;
if(constantExpression != null)
{
instanceName = "this";
}
var memberExpression = expr.Expression as MemberExpression;
if (memberExpression != null)
{
instanceName = memberExpression.Member.Name;
}
return CreateMessage(expr.Member.Name, expr.Member.MemberType.ToString(), instanceName);
}
private static string CreateMessage(MethodCallExpression expr)
{
string instanceName = null;
var newExpression = expr.Object as NewExpression;
if (newExpression != null)
{
instanceName = "new " + newExpression.Constructor.DeclaringType.Name;
}
var constantExpression = expr.Object as ConstantExpression;
if (constantExpression != null)
{
instanceName = "this";
}
var memberExpression = expr.Object as MemberExpression;
if (memberExpression != null)
{
instanceName = memberExpression.Member.Name;
}
return expr == null ? null : CreateMessage(expr.Method.Name, expr.Method.MemberType.ToString(), instanceName);
}
private static string CreateMessage(string memberName, string memberType, string declaringType)
{
var message = string.Format(
"{0}.{1} (Instance.{2})",
declaringType,
memberName,
memberType);
return message;
}
}
Its usage should be just on single methods or properties that must not be null but if they are, I want to know exactly where the null occurred:
class Foo
{
public string Bar { get; set; }
public string Baz() { return null; }
}
void Main()
{
string abc = null;
var foo = new Foo();
//NullGuard.Check(() => abc);
//NullGuard.Check(() => foo.Bar);
//NullGuard.Check(() => foo.Baz());
//NullGuard.Check(() => new Foo().Baz());
NullGuard.Check(() => qux());
}
string qux() { return null; }
Result:
foo.Bar (Instance.Method)
foo.Baz (Instance.Property)
this.qux (Instance.Property)
-
\$\begingroup\$ Interesting idea. Just keep in mind that this sort of thing is going to come with a performance cost. I don't know what that performance cost is, but it might be worth measuring if you plan to use this in any performance critical loops. \$\endgroup\$craftworkgames– craftworkgames2016年01月13日 23:42:01 +00:00Commented Jan 13, 2016 at 23:42
-
\$\begingroup\$ @craftworkgames thx for pointing this out however this is not my concern in most situations, sometimes I value more if I can find the cause for a bug easier especially if it runs on an important production server. Performance relevant code will contain optimized code. Fortunately most of the time it makes no difference if it costs even 30sec more :-) \$\endgroup\$t3chb0t– t3chb0t2016年01月14日 07:45:29 +00:00Commented Jan 14, 2016 at 7:45
-
\$\begingroup\$ @craftworkgames I've tested the performance and even a more complex expression needs in debug mode < 1ms - so no worry about it :) \$\endgroup\$t3chb0t– t3chb0t2016年01月15日 17:36:32 +00:00Commented Jan 15, 2016 at 17:36
1 Answer 1
Using a
NullReferenceException
for this seems wrong since nonull
reference has been dereferenced - it's capturing expressions which returnnull
when they are not expected to. AnInvalidOperationException
might fit better (since you are performing an operation on an object which is in an invalid state) or even your ownInvalidNullReturnException
.You can greatly simplify your message generation with:
var message = expr.Body.ToString();
This dumps the passed in expression as a string and you don't have to try and handle all kinds different expressions yourself.
Overall I would probably opt for aspect oriented programming (AOP) instead of littering my code with these
NullGuard
calls. There are a whole number of AOP frameworks around which usually work by using attributes you can decorate your methods and properties with. Based on these attributes arbitrary code can be injected - in your case you could inject thenull
checking and throwing code. This seems a much less intrusive way of achieving this kind of functionality greatly enhancing readability.
-
\$\begingroup\$ I'll check the 1. and 2. As far as the 3rd advice is concered this sounds good but unfortunatelly I cannot decorate 3rd party libs like that and I've been working (no choice) with really ugly APIs that all the time require calls like
a.b.c.d().e.f().g
. To check each part of it is annoying and not checking it causes evil bugs. If I could put it inside a null check it would save me a lot of time next time an error occurs... that's why I decided to extend the null guard and accept also chained expressions. \$\endgroup\$t3chb0t– t3chb0t2016年01月14日 19:12:44 +00:00Commented Jan 14, 2016 at 19:12 -
\$\begingroup\$ I've tested the
expr.Body.ToString()
and unfortunatelly I cannot use it because it produces a very unhelpful string like.Constant<NullGuardTests.NullGuardTests+<>c__DisplayClass4_0>(NullGuard.Tests.NullGuardTests+<>c__DisplayClass4_0)
\$\endgroup\$t3chb0t– t3chb0t2016年01月17日 11:27:58 +00:00Commented Jan 17, 2016 at 11:27 -
\$\begingroup\$ As to
AOP
it's also not a good solution because it seems to work only before a method execution started or after it has been executed. I see no way to insert some code inbetween when using other objects and properties other then checking the passed parameters. \$\endgroup\$t3chb0t– t3chb0t2016年01月17日 11:34:12 +00:00Commented Jan 17, 2016 at 11:34 -
\$\begingroup\$ @t3chb0t: I've only got experience with PostSharp and that lets you inject code anywhere you like. Not sure about the
ToString
- I've run you test cases through and they looked fine to me. \$\endgroup\$ChrisWue– ChrisWue2016年01月17日 18:12:21 +00:00Commented Jan 17, 2016 at 18:12
Explore related questions
See similar questions with these tags.