7
\$\begingroup\$

This small utility class is my solution for a more convenient way for checking against null. I also wanted to have more informative NullReferenceExceptions 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)

\$\endgroup\$
  • \$\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 Jan 13 '16 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 Jan 14 '16 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 Jan 15 '16 at 17:36
3
\$\begingroup\$
  1. Using a NullReferenceException for this seems wrong since no null reference has been dereferenced - it's capturing expressions which return null when they are not expected to. An InvalidOperationException might fit better (since you are performing an operation on an object which is in an invalid state) or even your own InvalidNullReturnException.

  2. 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.

  3. 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 the null checking and throwing code. This seems a much less intrusive way of achieving this kind of functionality greatly enhancing readability.

\$\endgroup\$
  • \$\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 Jan 14 '16 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 Jan 17 '16 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 Jan 17 '16 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 Jan 17 '16 at 18:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.