CONTROVERSIAL OPINION: The ?? Operator in C# is Not Necessary

Have you ever seen ?? in c# code? The first time you saw it, was your initial reaction “??”? The ??-operator was added in c# 2.0 and as the documentation says, “The ?? operator returns the left-hand operand if it is not null, or else it returns the right operand.” If you’re like me, you don’t like there to be too much syntax (and especially characters) in your language that is esoteric in its meaning (personally, I still prefer java’s “implement” and “extends” keywords over the c++/c# syntax of “:” for inheritence and implementation of interfaces).

Though I must include the disclaimer, there are degrees of how much I’m of the opinion. Maybe it’s just years of familiarity, but having some type of character, such as a semi-colon, terminating a line does not seem impractical. And the lambda expression syntax introduced in c# 3.x does provide a lightweight syntax for creating simple anonymous delegates that is really convenient.

The ??-operator, however, does not really yield any new functionality and it can be mimiced relatively easily with a method/function. In T-SQL/Sql Server, there is one already called ISNULL. The usage of such an implementation would not be too much. Admittedly, this entire (belated) complaint is a small quibble on my part, like preferring people to use the Nullable struct instead of int?, but still.

 

As an exercise in what I propose, I’ll include an implementation below and show some examples of how to use it. I’ve decided to use the Sql function’s naming convention in this case and call my functions IsNull and accomodate whether the value passed in is a Nullable struct or a class and if the default value is a value or if we want to execute a method to get our default value (relevant if the default value is calculated by retrieving records from a database). The latter case for the default value is what I think the motivating factor was in adding the syntax, since that needs to be done via a delegate, and creating an anonymous delegate really easily was introduced in a later version of c# (in the mean-time I had simply preferred sticking with if-else’s. Unsurprisingly, I’m not too keen on the ? : syntax that’s been around from the old c-days either).

As part of this example, I’m also including methods and examples used for setting null member variables in accessor properties (i.e. get’s), because I often find myself wanting to make sure an object’s List property is never null. This removes the need for the calling code to check for null values (Note, the member variable needs to be passed in by reference, so the value (which is itself a reference-type) will be modified. I’ll call this function, InitializeNull, since it is initializing null values.

If you want to use ?? in your property declaration and get it all into one line, your line of code would be:

get { return m_MemberVariable = m_MemberVariable ?? methodCall(); }

I don’t like that solution for a few reasons:

  • That code isn’t what one would consider clear (unless one has seen it a lot)
  • It is easy to make to make the error, “return m_MemberVariable ?? methodCall();”
    • Which would probably end up with the developer trying to figure out why all calls to the accessor is always returning an empty List (by contrast, the ref requirement on the InitializeNull argument will throw a compilation error if it’s missing).
    • Not immediately obvious to be wrong, since the way that .Net does a pretty good job of masking how strings are reference types, doing this with strings will probably give the right behavior (the error becomes a lot more obvious with reference types where you modify its properties)
  • Writing the member variable name twice makes longer member variable names feel even longer in that line of code

Examples of how the the InitializeNull method could be used (the IsNull method seems to obvious to even give examples):

 

Examples of the implementation of the methods:

using System; 				using System.Collections.Generic; 				using System.Linq; 				using System.Text;  				namespace DelphicSage.BlogPosts.IsNullInitialization 				{ 				public static class Helpers 				{ 				/// 				/// If the first argument is not null, return it.  If the first argument is null, return the second argument. 				/// 				/// 				/// 				/// 				/// 				public static TClass IsNull(TClass argument, TClass defaultValue) 				where TClass : class 				{ 				if (argument != null) 				return argument; 				else 				return defaultValue; 				}  				/// 				/// If the first argument is not null, return it.  If the first argument is null, return the return value of the second argument. 				/// 				/// 				/// 				/// 				/// 				public static TClass IsNull(TClass argument, Func getDefaultValue) 				where TClass : class 				{ 				if (argument != null) 				return argument; 				else 				return getDefaultValue(); 				}  				/// 				/// If the first argument is not null, return its value.  If the first argument is null, return the second argument. 				/// 				/// 				/// 				/// 				/// 				public static TStruct IsNull(Nullable argument, TStruct defaultValue) 				where TStruct : struct 				{ 				if (argument.HasValue) 				return argument.Value; 				else 				return defaultValue; 				}  				/// 				/// If the first argument is not null, return its value.  If the first argument is null, return the return value of the second argument. 				/// 				/// 				/// 				/// 				/// 				public static TStruct IsNull(Nullable argument, Func getDefaultValue) 				where TStruct : struct 				{ 				if (argument.HasValue) 				return argument.Value; 				else 				return getDefaultValue(); 				}  				/// 				/// If toInstantiate is not null, return it.  If it is null, instantiate toInstantiate by using its empty constructor and return itself. 				/// 				/// 				/// 				/// 				/// 				/// private List
m_Addresses = null; /// /// public List
Addresses /// { /// get { return HelperFunctions.InitializeNull(ref m_Address); } /// set { m_Address = value; } /// } /// public static TClass InitializeNull(ref TClass toInstantiate) where TClass : class, new() { if (toInstantiate == null) toInstantiate = new TClass(); return toInstantiate; } /// /// If toInstantiate is not null, return it. If it is null, set toInstantiate to the results of the method passed in (presumably a constructor) and return itself. /// /// /// /// /// /// private List
m_Addresses = null; /// /// public List
Addresses /// { /// get { return HelperFunctions.InitializeNull(ref m_Address, () => LoadAddresses()); } /// set { m_Address = value; } /// } /// public static TClass InitializeNull(ref TClass toInstantiate, Func constructor) where TClass : class { if (toInstantiate == null) toInstantiate = constructor(); return toInstantiate; } /// /// If toInstantiate is not null, return it. If it is null, set toInstantiate to an empty string and return itself. /// /// /// public static string InitializeNull(ref string toInstantiate) { if (toInstantiate == null) toInstantiate = String.Empty; return toInstantiate; } } }

Examples of how the the InitializeNull method could be used (the IsNull method seems to obvious to even give examples):

using System; 				using System.Collections.Generic; 				using System.Linq; 				using System.Text;  				namespace DelphicSage.BlogPosts.IsNullInitialization 				{ 				public class Person 				{ 				public class Address 				{ 				priv
ate string m_Address1; 				private string m_Address2;  				public string Address1 				{ 				get { return Helpers.InitializeNull(ref m_Address1); } 				set { m_Address1 = value; } 				}  				public string Address2 				{ 				get { return Helpers.InitializeNull(ref m_Address2); } 				set { m_Address2 = value; } 				} 				}  				private string m_FirstName = null; 				private string m_LastName = null; 				private string m_SSN = null; 				private List m_NickNamesExample_1_0 = null; 				private List m_NickNamesExample_1_1 = null; 				private List m_NickNamesExample_2_0 = null; 				private List m_NickNamesExample_2_1 = null;  				private List
m_Addresses_1_0 = null; private List
m_Addresses_1_1 = null; private List
m_Addresses_2_0 = null; private List
m_Addresses_2_1 = null; private List
m_Addresses_3_0 = null; private List
m_Addresses_3_1 = null; private List
m_Addresses_4_0 = null; public string FirstName { get { if (m_FirstName == null) m_FirstName = ""; return m_FirstName; } set { m_FirstName = value; } } public string LastName { get { return m_FirstName = m_FirstName ?? ""; } set { m_LastName = value; } } public string SSN { get { return Helpers.InitializeNull(ref m_SSN); } set { m_SSN = value; } } #region Examples of how one can set a property to an empty constructor call if it is null. // In all of these examples the constructor is only evaluated if the property is null. public List NickNamesExample_1_0 { get { if (m_NickNamesExample_1_0 == null) m_NickNamesExample_1_0 = new List(); return m_NickNamesExample_1_0; } set { m_NickNamesExample_1_0 = value; } } public List NickNamesExample_1_1 { get { return m_NickNamesExample_1_1 = m_NickNamesExample_1_1 ?? new List(); } set { m_NickNamesExample_1_1 = value; } } public List NickNamesExample_2_0 { get { return Helpers.InitializeNull(ref m_NickNamesExample_2_0); } set { m_NickNamesExample_2_0 = value; } } public List NickNamesExample_2_1 { get { return Helpers.InitializeNull(ref m_NickNamesExample_2_1, () => new List()); } set { m_NickNamesExample_2_1 = value; } } #endregion // Examples of how one can set a property to an empty constructor call if it is null #region // Examples of setting a property to the results of a method call if the property is null // In all of these examples the method is only evaluated if the property is null. Very relevant if the method // makes a database call to initialize the property. // Methods for creating the private List
loadAddresses() { return loadAddresses(this.SSN); } private List
loadAddresses(string ssn) { List
addressList = new List
(); // do some database lookup that uses the SSN as a parameter // use your imagination. return addressList; } ////////////////////////////////////////////////////////////////////////////////// /// Examples initializes properties to loadAddresses() if the property is null ////////////////////////////////////////////////////////////////////////////////// public List
Addresses_1_0 { get { if (m_Addresses_1_0 == null) m_Addresses_1_0 = loadAddresses(); return m_Addresses_1_0; } set { Addresses_1_0 = value; } } public List
Addresses_1_1 { get { return m_Addresses_1_1 = m_Addresses_1_1 ?? loadAddresses(); } set { m_Addresses_1_1 = value; } } public List
Addresses_2_0 { get { return Helpers.InitializeNull(ref m_Addresses_2_0, loadAddresses); } set { m_Addresses_2_0 = value; } } public List
Addresses_2_1 { get { return Helpers.InitializeNull(ref m_Addresses_2_1, () => loadAddresses()); } set { m_Addresses_2_1 = value; } } ////////////////////////////////////////////////////////////////////////////////// /// Examples initializes properties to loadAddresses(this.SSN) if the property is null ////////////////////////////////////////////////////////////////////////////////// public List
Addresses_3_0 { get { if (m_Addresses_3_0 == null) m_Addresses_3_0 = this.loadAddresses(this.SSN); return m_Addresses_3_0; } set { m_Addresses_3_0 = value; } } public List
Addresses_3_1 { get { return m_Addresses_3_1 = m_Addresses_3_1 ?? this.loadAddresses(this.SSN); } set { m_Addresses_3_1 = value; } } public List
Addresses_4_0 { get { return Helpers.InitializeNull(ref m_Addresses_4_0, () => this.loadAddresses(this.SSN)); } set { m_Addresses_4_0 = value; } } #endregion } }

I duplicated a lot of properties (with a varying suffix) so you can see what equivalent calls would be with each of the options presented. Which one you end up preferring will probably depend on you. However, I’m someone who prefers at least some words in my code hinting to make what I’m reading less obtuse, particularly when the syntactic shortcut does not necessarily gain you very much (which is more noticeable with the collection generic examples above than the string examples). So, I know which one I prefer.

« Prev Article
Next Article »