Is there a way to have a C# class handle its own null reference exceptions

前端 未结 7 1150
青春惊慌失措
青春惊慌失措 2021-02-05 05:05

Question

I\'d like to have a class that is able to handle a null reference of itself. How can I do this? Extension methods are the only way I can think

相关标签:
7条回答
  • 2021-02-05 05:09

    You can't reference a property if you do not have a valid instance reference. If you want to be able to reference a property even with a null reference and not put the onus of null-checking on the caller, one way is a static method in User:

    static bool IsAuthorized(User user)
    {
        if(user!=null)
        {
            return user.IsAuthorized;
        }
        else
        {
            return false;
        }
    }
    

    Then, when you want to check your authorization, instead of:

    if(thisUser.IsAuthorized)

    Do:

    if(User.IsAuthorized(thisUser))

    0 讨论(0)
  • 2021-02-05 05:11

    Can you use a struct instead? Then it should not ever be null.

    0 讨论(0)
  • 2021-02-05 05:18

    How about a proper Object Oriented solution? This is exactly what the Null Object design pattern is for.

    You could extract an IUser interface, have your User object implement this interface, and then create a NullUser object (that also implements IUser) and always returns false on the IsAuthorized property.

    Now, modify the consuming code to depend on IUser rather than User. The client code will no longer need a null check.

    Code sample below:

    public interface IUser
    {
        // ... other required User method/property signatures
    
        bool IsAuthorized { get; }
    }
    
    public class User : IUser
    {
        // other method/property implementations
    
        public bool IsAuthorized
        {
            get { // implementation logic here }
        }
    }
    
    public class NullUser : IUser
    {
        public bool IsAuthorized
        {
            get { return false; }
        }
    }
    

    Now, your code will return an IUser rather than a User and client code will only depend on IUser:

    public IUser GetUser()
    {
        if (condition)
        {
            return new NullUser(); // never return null anymore, replace with NullUser instead
        }
        return new User(...);
    }
    
    0 讨论(0)
  • 2021-02-05 05:18

    The only way this can work is if you use an extension method or other static method to handle the null reference.

    NullReferenceExceptions (NullPointerExceptions for the Javaheads; roughly synonymous) occur when code is told to call a method belonging to an object instance that doesn't actually exist. The thing you must keep in mind is that null is not actually any object. A variable of a type can be set to null, but that simply means the variable doesn't reference an instance.

    Therein lies the problem; if a variable, regardless of its type (as long as that's a nullable type), is null, then there isn't an instance on which to call the method, and an instance is required for instance methods because that's how the program determines the state of members accessible to the method. If MyClass had a MyField and MyMethod(), and you called MyMethod on a null reference of MyClass, what's the value of MyField?

    The solution is usually to move to static scope. Static members (and classes) are guaranteed to have state because they are instantiated once at runtime (usually just-in-time, as in before first reference). Because they always have state, they can always be called and so can be given things to do that may not be able to be done at the instance level. Here's a method you can use in C# to return a value from an object member chain which may otherwise result in an NRE:

    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection, 
           TOut defaultValue = default(TOut))
        {
            try
            {
                var result = projection(input);
                if (result == null) result = defaultValue;
                return result;
            }
            catch (NullReferenceException) //most nulls result in one of these.
            {
                return defaultValue;
            }
            catch (InvalidOperationException) //Nullable<T>s with no value throw these
            {
                return defaultValue;
            }
        }
    

    Usage:

    class MyClass {public MyClass2 MyField;}
    class MyClass2 {public List<string> MyItems; public int? MyNullableField;}
    
    ...
    var myClass = null;
    //returns 0; myClass is null
    var result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
    myClass = new MyClass();
    //returns 0; MyField is null
    result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
    myClass.MyField = new MyClass2();
    //returns 0; MyItems is null
    result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
    myClass.MyField.MyItems = new List<string>();
    //returns 0, but now that's the actual result of the Count property; 
    //the entire chain is valid
    result = myClass.ValueOrDefault(x=>x.MyField.MyItems.Count);
    //returns null, because FirstOrDefault() returns null
    var myString = myClass.ValueOrDefault(x=>x.MyField.MyItems.FirstOrDefault());
    myClass.MyField.MyItems.Add("A string");
    //returns "A string"
    myString = myClass.ValueOrDefault(x=>x.MyField.MyItems.FirstOrDefault());
    //returns 0, because MyNullableField is null; the exception caught here is not an NRE,
    //but an InvalidOperationException
    var myValue = myClass.ValueOrDefault(x=>x.MyField.MyNullableField.Value);
    

    While this method has value in situations which would otherwise call for long nested ternary operators to produce something (anything) to show the user or use in a calculation, I do NOT recommend using this pattern to perform actions (void methods). Because no NREs or IOEs will be thrown out, you'll never know if what you asked it to do was actually done. You may be able to get away with a "TryPerformAction()" method which returns true or false, and/or has an output parameter containing the thrown exception (if any). But if you're going to go to that kind of trouble, why not just try/catch the thing yourself?

    0 讨论(0)
  • 2021-02-05 05:29

    If the variable is null, it means it references no object, therefore is makes no sense (and I think its not technically possible) to handle the null reference inside the class method.

    You should guarantee that it is not null, either by checking just before calling "IsAuthorized" or event before.

    Edit: Finding an workaround to do that would be a bad thing: it would be confusing for someone to understand the behavior, since it is not the "expected" behavior for the programming language. It could also cause your code to hide some problem (a null value where it should be an object) and create a bug that will be hard to find. That said: it is for sure a bad idea.

    0 讨论(0)
  • 2021-02-05 05:30

    However, when my User reference contains null I'd like IsAuthorized to always return false instead of exploding.

    This can only be done if IsAuthorized is a static method, in which case you can check for null. This is why extension methods can do this - they are really just a different syntax for calling a static method.

    Calling a method or property, such as IsAuthorized as an instance method requires an instance. Just the act of calling an instance method (including a property getter) on null will trigger the exception. The exception isn't raised by your class, but by the runtime itself when you attempt to use the (null) reference. There is no way around this in C#.

    0 讨论(0)
提交回复
热议问题