C# Coding Solutions—A Null Value Is Not Always a Null State

Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio


Jump to: navigation, search
CSharp-Online.NET:Articles
C# Articles

C# Coding Solutions

© 2006 Christian Gross

A Null Value Is Not Always a Null State

Developers attempt to write the best code they can, but they often make mistakes when complex objects are involved. Frequently a class will require a return value, and for ease of coding a null value is returned even though the state is inconsistent.

The following code illustrates how returning a null value is the wrong thing to do:

class ReferenceObject {
    public void GenerateOutput() {
        Console.WriteLine( "Generated");
    }
}
class InconsistentState {
    public ReferenceObject GetInstance() {
        return null;
    }
    public void CallMe() {
        GetInstance().GenerateOutput();
    }
}

In the example the class InconsistentState has two methods: GetInstance and CallMe. The method CallMe calls the method GetInstance. Though GetInstance always returns a null value, that is not checked for validity in the implementation of CallMe. That means an exception will be generated when it is not necessary. As a solution in most cases developers would add a decision to test whether GetInstance returns a valid instance.

You can fix the problem of a valid instance by saying that there is always a consistent state represented by a default object. This is where nullable types and the Null Object pattern come into play.

The following example shows how nullable types solve the problem of inconsistent state:

class NullableReturn {
    public int ReturnValue() {
        int? returnvalue = null;
        return (int)returnvalue;
    }
}

The class NullableReturn has a single method ReturnValue, which returns a value type. The variable returnvalue is assigned a value of null indicating an inconsistent state. When the variable returnvalue is cast to an integer, the cast is saying, "Return an integer value." In the example, the nullable type returnvalue is assigned null, resulting in an exception. The exception is generated by the nullable type because the nullable type cannot extract any value. Of course, comparing this solution to the previous, which also generated an exception, you would be tempted to say that there is no advantage. There is a difference in that the nullable generated exception could be expected, whereas the null object reference is not expected.

Nullable types solve our value type problem, but what happens when reference types are used? Often programmers will return the wrong value. Consider the following source code, which offers three variations on a reference type that can be returned from a method without using a nullable type:

class HowToReturn {
    public IList<string> Variation1() {
        return null;
    }
    public IList<string> Variation2() {
        return new List< string>();
    }
    public IList<string> Variation3() {
        throw new InvalidOperationException();
    }
}

When implementing your own application one of the three variations will have to be used; the question is, which one? Using the nullable-type example that generated an exception, Variation3 would be the best answer. However, that is not correct because the context has been properly defined—we don’t know what is being attempted and why a valid value can’t be returned. Adding a context reveals that each of the methods is appropriate. Variation1 is wrong in 95 percent of contexts. Variation2 is appropriate for most cases if there is no data to return. Variation3 is appropriate when there might be data to return but something went wrong in the execution of the method. So in most cases you will use Variation2; the following example illustrates why:

HowToReturn cls = obj.getInstance();
 
foreach( string item in cls.Variation2()) {
    Console.WriteLine( "Item (" + item + ")");
}

The method obj.getInstance is called and instantiates the class HowToReturn that is assigned to the variable cls. Then a foreach loop is started. The code is living dangerously because there is no test to verify that cls is a valid object instance. If Variation1 were used to implement getInstance, an additional test would be required. That would complicate the solution and require additional coding of a test.

The proper solution is to implement getInstance using Variation2. Variation2 is the proper solution because the code can be expected to manipulate a valid instance at all times. The valid instance might be an empty collection, and that is OK because the foreach loop will not execute.

When a problem does arise, you’ll use Variation3. This means if a problem due to an error arises in the execution of a method, an exception is thrown. Often developers are hesitant to throw exceptions because they are expensive. But an error is an error, and should be marked as an error. Returning a null value to indicate an error complicates things and requires a developer to figure out what went wrong.

As I mentioned, you’ll rarely use Variation1, but the following source code illustrates when returning a null value is preferred action:

Hashtable table = new Hashtable();
Console.WriteLine( "Element (" + table[ "something"] + ")");

In the example, a Hashtable is instantiated and nothing is added. The next line retrieves from the hash table the object associated with the key "something". If the Hashtable returned a value based on the logic of Variation2 and Variation3, the consumer would be left wondering what to do. The logic of Variation1 is appropriate because querying a collection for a specific value, we can expect to be returned a null value indicating no value. In the case of Variation2 it is an incorrect implementation because returning an empty value implies that the Hashtable has some value. And Variation3 is incorrect because a null value is an appropriate response for an item that does not exist in the collection.

The Hashtable class has a solution that improves the reasoning for returning a null value. It has an additional method that you can use to query if a value exists; this takes the place of making a decision based on a null value:

if( table.Contains( "something")) {
    Console.WriteLine( "Element (" + table[ "something"] + ")");
}
else {
    Console.WriteLine( "Does not contain");
}

The method Contains will query the hash table to see if a valid value is available. If so, then the hash table indexer can be retrieved. The presented solution is correct, but there is a problem. The hash table is queried twice, which could mean wasting resources. The first query iterates the elements to see if the element exists, and the second iterates and retrieves the element. An application could provide some optimizations, but should the hash table do it instead? This is one of those 5-percent situations in which it is acceptable to return a null value.

Now that you’re familiar with all three variations and you know that Variation2 is preferred, the Null Object pattern needs further explanation. It is an implementation of Variation2 and is used to define objects that are null values. The Null Object pattern solves null-value problems because you assign the variable a type instance that does nothing.

Illustrated will be how the Null Object pattern is exposed and implemented. The next set of declarations illustrates the Null Object pattern:

public interface ILightState {
    bool IsLightOn {
        get;
    }
    void TouchButton();
}

A Null Object pattern always starts with a base class or an interface because you want the consumer of the interface or base class to think it is manipulating a valid instance. The Null Object pattern implementation is usually a distinct and minimalist implementation. You might be tempted to use an already existing implementation and modify it to support a default empty behavior, but that would be incorrect because a null value usually is an empty immutable object. In the case of the ILightState interface, a Null Object pattern implementation would be as follows:

public class NullLightState : ILightState {
    public bool IsLightOn {
        get {
            throw new NotImplementedException();
        }
    }
}

Notice that instead of returning false for the property IsLightOn, an exception is thrown. That’s because for the null object in this example, a state does not exist. The light is either on or off—there is no in-between. There is a default state, but that is not a null state. To understand the difference between null states and default states consider the following implementation of the State pattern, which acts like a light switch:

public class LightSwitch {
    private ILightState _onLight = new LightStateOn();
    private ILightState _offLight = new LightStateOff();
    private ILightState _current = _offLight;
 
    public ILightState LightState {
        get { return _current; }
    }
    public void TouchButton() {
        if(_current.IsLightOn) {
            _current = _offLight;
        }
        else {
            _current = _onLight;
        }
    }
}

The class LightSwitch manages a set of states, which are assigned by the method TouchButton. The data member _current is assigned _offLight, meaning that when LightSwitch is instantiated the switch will be off.

Using the Null Object pattern would be appropriate if something went wrong while an instance of LightSwitch was being manipulated. For example, let’s say that the light switch overloaded and blew a fuse. Then the light switch has neither an on nor an off state. The light switch blew a fuse and therefore _current must reference an instance of NullLightState.

In this example of the Null Object pattern, the implementation threw an exception. Is this the correct implementation for all situations? The answer is that the Null Object pattern implementation depends on what an appropriate object instance response is when manipulated. Following are some examples and the appropriate responses:

  • Collections. An empty collection is an appropriate implementation because an empty collection indicates that everything is OK but that there is no data.
  • Proxy pattern. A Null Object pattern implementation should generate an exception for the specified conditions. The idea behind the Proxy pattern is to provide a call-through mechanism. You would use the Null Object pattern if you want to indicate certain unsupported, unimplemented, or unusable functionality. For example, imagine having a read-write interface and writing to a read-only CD-ROM. Writing to the read-only CD-ROM is impossible and would require changing all the routines in the write modules to check if it is OK to write. Or you could use the Proxy pattern with a read-only Null Object pattern implementation. Then the write routines could be left as is, and the client would never call them because the read-only Null Object pattern would intercept the calls and generate an exception.
  • Decorator pattern. A Null Object pattern implementation would act as a pass-through. The idea behind the Decorator pattern is to chain together some implementations and enhance the functionality of the decorated object. The Null Object pattern is a placeholder and thus should not interfere and should remain silent when queried.

Remember the following points when writing code that needs to return a null object instance because there is no valid object instance:

  • There are three main ways of returning a null object instance: returning null, returning an empty value, and throwing an exception. Which one you should use depends on the context.
  • The Null Object pattern can be used in conjunction with interfaces and base classes when returning an empty value.
  • A Null Object pattern implementation cannot be generalized as being only one type of implementation. It is one of the few patterns that varies and has its implementation dependent on the context.
  • Developers do not think enough about what the value null represents. It seems harmless, and it is easy to test for a null-value condition. However, null means an inconsistent state, and developers need to understand and define what a correct state is for their applications.


Previous_Page_.gif Next_Page_.gif


Personal tools