When writing software there are mismatches between the modeling tools and languages we use and the things we are modeling. We create entities in code as part of the model we have built and they are stored in memory in the execution context of our application. We have references to the place in memory where our model is stored that we pass around instead of passing around the whole entity. Otherwise we would end up having to copy the whole thing. The mismatch comes in to play when our reference or pointer does not point to anything.
Null References
For example, let's say we have a person:
- public class Person
- {
- private readonly Int32 _Id;
- private readonly String _Name;
- private Person(Int32 id, String name)
- {
- _Id = id;
- _Name = name;
- }
- public Int32 Id
- {
- get
- {
- return _Id;
- }
- }
- public String Name
- {
- get
- {
- return _Name;
- }
- }
- }
- public void Display(Person person)
- {
- Console.WriteLine(person.Name + ":" + person.Id);
- }
Because we know this is an invalid state for the method, we should help to make debugging easier by adding a guard clause to assert that the value coming in is valid.
- public void Display(Person person)
- {
- if (ReferenceEquals(person, null))
- {
- throw new ArgumentNullException("person");
- }
- Console.WriteLine(person.Name + ":" + person.Id);
- }
Null is Another Type of State
The second, more subtle, problem we have is that now there is another state we have to deal with which drives complexity into the system.
Looking at the simplest case, if we have a nullable boolean there are now three states we have to worry about. Boolean is a CLR value type and cannot be null. But if we make the boolean nullable with the syntax below, we now have a tertiary state that has to be addressed.
- Nullable<Boolean> value = true;
- Nullable<Boolean> value = false;
- Nullable<Boolean> value = null;
- Boolean? value = true;
- Boolean? value = false;
- Boolean? value = null;
- public void CheckSomething(Boolean ? value)
- {
- if (ReferenceEquals(value, null)) // or value.HasValue
- {
- // do something for the extra case
- }
- else if (value.Value)
- {
- /// handle 'true' case
- }
- else
- {
- // handle 'false' case
- }
- }
- public void CheckSomething(Boolean value)
- {
- if (value)
- {
- /// handle 'true' case
- }
- else
- {
- // handle 'false' case
- }
- }
- public static void GetState(Boolean ? value)
- {
- var refEqualsNull = ReferenceEquals(value, null);
- var equalsNull = value == null;
- var hasValue = value.HasValue;
- }
Modeling and the Null State
The big problem is when the null state is not aligned with the model we are trying to build which is intended to represent a small slice of reality in order to provide business value. Good code is closely aligned with the domain it is modeling. Great code is part of a ubiquitous language that permeates not only all the technical layers but also determines how business and technical experts talk about the system. The concept of "null" is a part of the computer science domain that leaks into the business domain being modeled. It is not well-aligned with business concepts.
For example, if we are having a discussing about how people are related with a non-technical business domain expert to clarify the following code:
- var car = new Automobile();
- var road = new Road();
- if (car.CanTravelOn(road))
- {
- // do something
- }
- public void Traverse(Automobile car, Road[] route)
- {
- if (car == null) // equality as a concept does not make sense here
- {
- // handle null case
- }
- foreach(var road in route)
- {
- if (road == null) // now we are outside the domain
- {
- // handle null case
- }
- if (car.CanTravelOn(road))
- {
- // do something
- }
- }
- }
Instead of allowing nulls throughout our code base, it is clearer to have a special case or instead of having repeated checks for null, use the more specific NullObject pattern as prescribed by Martin Fowler. If this is rigorously enforced through the code base then our in-line null checks are no longer necessary. In addition, if we use more explicit language that clearly is checking references instead of using equality "==" there is less chance for miscommunication with business experts. If we are consistent about placing guards at the top of every method to enforce correct consumption and don't let the guard clauses mix with the business logic, the code will be much clearer from a business perspective.
- public class NoRoad: Road // instead of passing nulls, pass this
- {
- public Int32 Miles
- {
- get
- {
- return 0;
- }
- }
- }
- public void Traverse(Automobile car, Road[] route)
- {
- /// Enforce correct consumption
- if (ReferenceEquals(car, null))
- {
- throw new ArgumentNullException("car");
- }
- /// Business logic starts here
- foreach(var road in route)
- {
- if (car.CanTravelOn(road))
- {
- // do something
- }
- }
- }
- // option: store NullObject reference to use instead of throwing.
- public void Traverse2(Automobile car, Road[] route)
- {
- /// Make sure we have a car
- var car = car ? ? this.MissingCar; // store NullObject at the class level
- /// Business logic starts here
- foreach(var road in route)
- {
- if (safeCar.CanTravelOn(road))
- {
- // do something
- }
- }
- }
A subclass that provides special behavior for particular cases: Martin Fowler
Indeterminate Behavior With "==" Operator and ".Equals()" Method
According to the Microsoft Guideline
Unlike the Equals method and the equality operator, the "ReferenceEquals()" method cannot be overridden. Because of this, if you want to test two object references for equality and you are unsure about the implementation of the Equals method, you can call the "ReferenceEquals()" method.
Any healthy code base will change over time. We cannot guarantee that the Equals() methods or equality operators "==" will have consistent behavior over time. Therefore, to have a more stable code base it is better to have explicit reference checks rather than relying on indeterminate behavior. In the worst case, having indeterminate code throughout the code base will cause defects that are very hard to diagnose, because a change in the class will change code in unknown places and we can have a butterfly effect from changes in our code base.
The argument could be made whether equality and the equality operator should be overridden. In many cases overriding operators does not make sense. But this is a debate that needs to be taken on a case-by-case basis. The fact that we have the ability to override the equality operator is a language feature in C# means that the possibility exists. We can carefully inspect all changes going into our code bases to help try and avoid equality overriding if we have determined it should not be done. While auditing can help, it does not guarantee a stable code base. Irregardless of where the debate lands on the usage of this language feature, if we want a stable code base it is better to use the "ReferenceEquals()" method to check for null references.
Recommendation For Working With Null
Keep code determinate, simple and well aligned with business. This can be accomplished by explicitly checking for null. If we are talking about equality, try to leave null out of the equation.
This will keep your code base stable, reduce the chance for defects to be introduced and facilitate development momentum.
Until next time
Happy Coding
Here is my recommendation for implementing equality in C#.
[Original article]
Vivek KumarPosted Apr 9, 2016, 4:33 PM
Nice one
Chiheb ChebbiPosted Jun 29, 2015, 10:17 AM
good article
Bruno PétersonPosted Jun 18, 2015, 5:46 AM
Good one
Dhanik SahniPosted Apr 27, 2015, 3:44 AM
Good
Gowtham RajamanickamPosted Apr 9, 2015, 8:44 AM
simply awesome
Santhakumar MunuswamyPosted Apr 9, 2015, 7:12 AM
thanks for nice one
Gowtham RajamanickamPosted Apr 7, 2015, 7:33 AM
nice