Sunday, December 13, 2009

Nullable Data Types Demo

The Problem

In several of the projects I’ve worked on, we used some token value to represent null for non-nullable types. These values would be extreme values (for int, we would use int.MinValue or int.MaxValue), or null token. Recently I’ve come across some controls that don’t like this scheme and prefer using nullable types (like int?).

The original code was written in C# 1.X, so nullable types didn’t exist; the code works, so I don’t want to convert the non-nullables to nullable and risk breaking the rest of the application.

In production, I’ve solved the problem by creating “shadow” nullable properties. The code in these properties would translate the null tokens to null. For the most part the translation code is inline.

Taking it to 11 with Generics

This weekend, I played with implementing this stuff with a generic helper class. So the code for a nullable property would look something like this:

public DateTime? DobNullable
{
     get { return NullableHelper.GetNullable<DateTime>(this.Dob); }
     set { this.Dob = NullableHelper.SetNullable<DateTime>(value); }
}

To do this I created a class called NullableHelper that contained translation functions: The to set you use this:

public static T SetNullable<T>(T? futureValue)
     where T : struct
{
     T rVal;
     if (futureValue != null)
          rVal = (T)futureValue;
     else
          rVal = NullForT<T>();
     return rVal;
}

This function echo back the value passed in UNLESS it is null; if it is null, it returns the null token. And to get, you use this:

public static T? GetNullable<T>(T presentValue)
     where T : struct, IComparable
{
     T? rVal = null;

     // I can't get the generic do == & !=, this works, so I'm 
     // going with it for now.
     IComparable ic = presentValue as IComparable;
     if (ic != null)
     {
          T CompareValue = NullForT<T>();
          if (ic.CompareTo(CompareValue) != 0)
               rVal = presentValue;
          // I already set rVal to null above.
          return rVal;
     }
     else
     {
         throw new ApplicationException(
             string.Format("NullableDataTypesDemo.NullableHelper.GetNullable<T>()" +
                  "\r\n'{0}' is can't be cast to IComparable",
                  typeof(T).ToString())
          );
     }
}

In this function I needed to cast T as IComparable to compare the value with the null token.

Both functions use a null token from the following function:

public static T NullForT<T>() 
     where T : struct
{
     T rVal;
     // Here you will need to have an if for each of the 
     // supported types, this function could be really big in production:
     if (typeof(T) == typeof(DateTime))
     {
          DateTime x = DateTime.MaxValue;
          object o = x;
          rVal = (T)o;
     }
     // Set other null tokens here
     else
     {
          throw new ApplicationException(
                 string.Format("NullableDataTypesDemo.NullableHelper.NullForT<T>()" +
                 "\r\n'{0}' is an unsupported Type", 
                 typeof(T).ToString())
          );
     }
     return rVal;
}

This function is probably the Achilles’ heel of this design. You will need an if (typeof(T) block for each type you want to support in this function. Most of the complexity points of this solution are borne by this function.

Conclusion

Over the weekend I came up with a cool way of translating from a non-nullable with null tokens to nullables. Will I implement this in production? Probably not, the code works and I don’t want to risk breaking it. If I need to do similar things in a future project, I may come back to this post.

The Whole Experiment

using System;

namespace NullableDataTypesDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create an instance of the demo entity
            DemoEntityClass x1 = new DemoEntityClass(
                1, 
                100m,
                "Jill Stephens", 
                NullableHelper.NullForT<DateTime>(), 
                NullableHelper.NullForT<int>());

            // Do some querying
            if (x1.IdNullable != null)
            {
                Console.WriteLine(string.Format("ID = {0}", x1.IdNullable));
            }
            if (x1.NumOfChildren == NullableHelper.NullForT<int>())
            {
                Console.WriteLine("Children Unknown (classic)");
            }
            // Set a DOB
            x1.DobNullable = new DateTime(1776, 7, 4);
            Console.WriteLine(string.Format("{0:d}", x1.DobNullable));
            // Set # of children and display
            x1.NumOfChildrenNullable = 2;
            Console.WriteLine("value of Number of Children: {0}", x1.NumOfChildren);
            // Unset # of children
            x1.NumOfChildren = NullableHelper.NullForT<int>();
            if (x1.NumOfChildrenNullable == null)
            {
                Console.WriteLine("Children Unknown (new)");
            }
            // Won't comile, 
            // NullableHelper.NullForT<DemoEntityClass>();
            // Some NullForT that won't compile
            // (you can fix these by adding code to NullForT())
            try
            {
                NullableHelper.NullForT<DemoStruct>();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            try
            {
                NullableHelper.NullForT<double>();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            // Force read line, so this won't go away in VS
            Console.ReadLine();
        }

    }
    /// <summary>
    /// My Demo Entity Class
    /// </summary>
    class DemoEntityClass
    {
        public DemoEntityClass(int id, decimal amount, string name, DateTime dob, 
            int numOfChildren)
        {
            this.Id = id;
            this.Amount = amount;
            this.Name = name;
            this.Dob = dob;
            this.NumOfChildren = numOfChildren;
        }
        public int Id { get; set; }
        public decimal Amount { get; set; }
        public string Name { get; set; }
        public DateTime Dob { get; set; }
        public int NumOfChildren { get; set; }

        public int? IdNullable
        {
            get { return NullableHelper.GetNullable<int>(this.Id); }
            set { this.Id = NullableHelper.SetNullable<int>(value); }
        }
        public decimal? AmountNullable
        {
            get { return NullableHelper.GetNullable<decimal>(this.Amount); }
            set { this.Amount = NullableHelper.SetNullable<decimal>(value); }
        }
        // string is nullable, so I am including this for completeness
        public string NameNullable
        {
            get { return this.Name; }
            set { this.Name = (string)value; }
        }
        public DateTime? DobNullable
        {
            get { return NullableHelper.GetNullable<DateTime>(this.Dob); }
            set { this.Dob = NullableHelper.SetNullable<DateTime>(value); }
        }
        public int? NumOfChildrenNullable
        {
            get { return NullableHelper.GetNullable<int>(this.NumOfChildren); }
            set { this.NumOfChildren = NullableHelper.SetNullable<int>(value); }
        }
    }
    /// <summary>
    /// My Demo struct
    /// </summary>
    struct DemoStruct
    {
        public int x;
        public int y;
        public int xy { get { return x * y; } }
    }
    static class NullableHelper
    {
        /// <summary>
        /// Gets the Null marker value for value types
        /// </summary>
        /// <remarks>
        /// In some old applications that date back to before nullable types
        /// maked fields as being null with extreme values (int.MaxValue for
        /// example).
        /// </remarks>
        /// <typeparam name="T">Tye value type being tested</typeparam>
        /// <returns>The Null marker value</returns>
        public static T NullForT<T>() 
            where T : struct
        {
            T rVal;
            // Here you will need to have an if for each of the 
            // supported types, this function could be really big in production:
            if (typeof(T) == typeof(int))
            {
                int x = int.MaxValue;
                object o = x;
                rVal = (T)o;
            }
            else if (typeof(T) == typeof(decimal))
            {
                decimal x = decimal.MaxValue;
                object o = x;
                rVal = (T)o;
            }
            else if (typeof(T) == typeof(string))
            {
                string x = string.Empty;
                object o = x;
                rVal = (T)o;
            }
            else if (typeof(T) == typeof(DateTime))
            {
                DateTime x = DateTime.MaxValue;
                object o = x;
                rVal = (T)o;
            }
            else
            {
                throw new ApplicationException(
                    string.Format("NullableDataTypesDemo.NullableHelper.NullForT<T>()" +
                      "\r\n'{0}' is an unsupported Type", 
                    typeof(T).ToString())
                );
            }
            return rVal;
        }
        /// <summary>
        /// Used in a set block to set a non-nullable
        /// field from a nullable value
        /// </summary>
        /// <remarks>
        /// Since the null values are gotten from NullForT, this function can be short
        /// </remarks>
        /// <see cref="NullForT"/>
        /// <typeparam name="T">Tye value type being set</typeparam>
        /// <param name="futureValue">nullable new value</param>
        /// <returns>non nullable value  with Null marker value to denote null</returns>
        public static T SetNullable<T>(T? futureValue)
            where T : struct
        {
            T rVal;
            if (futureValue != null)
                rVal = (T)futureValue;
            else
                rVal = NullForT<T>();
            return rVal;
        }
        /// <summary>
        /// Used in a get block to get a nullable field
        /// from the non-nullable value
        /// </summary>
        /// <remarks>
        /// This function is a little more complex because we need to cast
        /// presentValue to IComparable to compare it to the null token 
        /// </remarks>
        /// <see cref="NullForT"/>
        /// <typeparam name="T">Tye value type being set</typeparam>
        /// <param name="presentValue">
        /// non-nullable value with Null marker value to denote null
        /// </param>
        /// <returns>nullable value returned</returns>
        public static T? GetNullable<T>(T presentValue)
            where T : struct, IComparable
        {
            T? rVal = null;

            // I can't get the generic do == & !=, this works, so I'm 
            // going with it for now.
            IComparable ic = presentValue as IComparable;
            if (ic != null)
            {
                T CompareValue = NullForT<T>();
                if (ic.CompareTo(CompareValue) != 0)
                    rVal = presentValue;
                // I already set rVal to null above.
            }
            else
            {
               throw new ApplicationException(
                 string.Format("NullableDataTypesDemo.NullableHelper.GetNullable<T>()" +
                      "\r\n'{0}' is can't be cast to IComparable",
                 typeof(T).ToString())
               );
            }
            return rVal;
        }
    }
}