devxlogo

Working with Nullable Types in C#

Working with Nullable Types in C#

orking with value types was sometimes awkward in .NET 1.x, because they could cause serious problems in applications that retrieved data from database tables containing columns that can have null values?those that may or may not hold data. C# 2.0 presents a new concept called “nullable types” that lets you initialize value types to null values. This article discusses how you can apply the new nullable types feature to solve issues that result from having null values in database columns.

Why Nullable Types?
Value types are “primitive” data types such as numbers. The .NET Framework stores value types in the stack and implicitly initializes them to their default values?even if you do not explicitly initialize them when they are defined. For example, an int gets initialized to 0, a bool to false, and so on. Unfortunately, most value types are unable to represent a null value, which presents a problem when you’re working with data-centric applications where null values are possible, because you have to select some other value to represent null. But whatever value you choose to represent null is no longer available as a valid data value, which restricts the range of permissible values. In other words, if you elect to use -1 to represent null, you’ve removed -1 as a valid value from the range of numbers that the value type can support. In addition, you must check to ensure that the chosen “null-representation” values are ignored in other parts of the application?and you must make sure not to display the null-representation values directly to end users, who may not understand that the value represents null.

Developers have tried various approaches to solve the null-value type mismatch, but each has its associated pitfalls. This is where nullable types can help. They’re perfect for situations where you may need to represent a value type in an undefined state.

The most common such situation occurs when you want to assign the value from a column of a database table (that allows for null values) to a C# type.

The System.Nullable Structure
C# 2.0 provides a System.Nullable generic type struct that you can use to define nullable types. The constructor accepts one parameter?the type itself?and is defined as shown below:

   namespace System   {      public struct Nullable : System.IComparable,          System.INullableValue      {         public Nullable(T value);         public static explicit operator T(T? value);         public static implicit operator T?(T value);         public T Value { get; }         public bool HasValue { get; }         public T GetValueOrDefault();      }   }

Here’s the syntax you use to define a nullable type:

   System.Nullable variable = null;

Note that the generic type T in the preceding code stands for a value type, not a reference type.

As a more concrete example, you can define a nullable integer type as follows:

   System.Nullable patientBilledAmount;

The long version above has an equivalent shorthand version:

   int? patientBilledAmount;

In C# 2.0, a question mark (?) suffix following a data type designator specifies that the type can accept null values.

If you refer back to the nullable struct definition shown earlier, notice that any nullable type contains two properties that you use in tandem to check for null values. If the type contains a non-null value, the HasValue property returns true; otherwise it returns false. On the other hand, the Value property returns a non-null value if and only if the HasValue property returns true; otherwise, it throws an exception. So typically, you check nullable type values as follows:

   if (patientBilledAmount.HasValue)      Console.WriteLine(patientBilledAmount.Value);    else      Console.WriteLine("The amount is null");   
Author’s Note: Always perform the HasValue check, because if you try to retrieve the value of a nullable type using its Value property, and HasValue is false, the .NET Framework throws an InvalidOperationException.

Implementing Nullable Types in C#
There may be situations where you might have to assign data from database columns containing null values to equivalent C# types. This section presents a simple application that will illustrate how to use nullable types to mitigate such problems.

?
Figure 1. Sample Table: Notice that three of the columns in this table allow null values.

Figure 1 shows the structure of a table called Patient that contains three nullable fields.

Executing the SQL script in Listing 1 creates the Patient table in your database (see Figure 1). You need some sample data, too; the script shown below inserts several records into the Patient table:

   SET IDENTITY_INSERT [PatientInfo] ON      INSERT INTO [dbo].[PatientInfo] (     [PatientID], [Name], [ContactPerson], [SSN],      [BilledAmount], [Address], [Sex],      [DateOfBirth], [AdmissionDate],      [ReleaseDate])      VALUES (       1, 'Joydip', NULL, 4234234, NULL,        'Flat 20, Suvarna Aparments, Gachiboli, Hyderabad',        'Male', '19671102', '20070902', '20070910')      INSERT INTO [dbo].[PatientInfo] (     [PatientID], [Name], [ContactPerson], [SSN],      [BilledAmount], [Address], [Sex], [DateOfBirth],      [AdmissionDate], [ReleaseDate])      VALUES (       2, 'Vinay', NULL, NULL, 500,        'Saptagiri Towers, Begumpet, Secunderabad',        'Male', '19770808', '20070821', '20070910')      INSERT INTO [dbo].[PatientInfo] (     [PatientID], [Name], [ContactPerson], [SSN],      [BilledAmount], [Address], [Sex], [DateOfBirth],      [AdmissionDate], [ReleaseDate])      VALUES (       3, 'Sriram', NULL, NULL, NULL,        '2-4/A,Parklane Road, Hyderabad', 'Male',        '19790503', '20070608', '20070621')      INSERT INTO [dbo].[PatientInfo] (     [PatientID], [Name], [ContactPerson], [SSN],      [BilledAmount], [Address], [Sex], [DateOfBirth],      [AdmissionDate], [ReleaseDate])      VALUES (       4, 'Nageswar', 'Ashant', 83283493, 25000,        '3-6/W, Keys Marg, Secunderabad', 'Male',        '19670522', '20070406', '20070422')      SET IDENTITY_INSERT [PatientInfo] OFF      GO

The three nullable fields in the Patient table are ContactPerson, BilledAmount, and SSN; those fields may or may not have values.

A Simple Null-Aware Application
With the sample data available, here’s a simple application that illustrates how and where you can use nullable types.

The application has two classes called PatientNullable and PatientNonNullable. These classes read data from the Patient table discussed in the preceding section, and use that data to populate class members that correspond to the table columns. Both classes have a GetPatient() method that accepts a PatientID parameter, queries the database for the patient with that PatientID, and populates the class instance with the retrieved data.

The difference between the two classes, of course, is that the PatientNonNullable class does not handle nullable types; therefore, it’s prone to problems at run time if you attempt to populate it from from database columns that can contain null values. In contrast, the PatientNullable class does accept null types. The differences in the two GetPatient() methods should clarify where nullable types can help simplify your database application development. Listing 2 and Listing 3 show the full code for the PatientNullable and PatientNonNullable classes, respectively, but here’s the relevant portion from the getPatient() methods in both classes. The two fragments below assign values from from a DataReader containing query fields that might be null to the ContactPerson, BilledAmount, and SSN class properties.

Here’s the PatientNonNullable.getPatient() code excerpt:

   ...      contactperson = dataReader["ContactPerson"] ==          DBNull.Value ? null :          dataReader["ContactPerson"].ToString();      if (dataReader["SSN"] == DBNull.Value)         ssn = -1; //NULL replaced with -1         else         ssn = Convert.ToInt64(dataReader["SSN"]);      if (dataReader["BilledAmount"] == DBNull.Value)         billedamount = 0; //NULL Replaced with Zero      else         billedamount = Convert.ToInt32(            dataReader["BilledAmount"]);   ...

The ContactPerson string field has no problems with accepting null values, but when the SSN or BilledAmount database columns contain a nullable value, the PatientNonNullable class must substitute something to provide a valid value for the associated property’s non-nullable value type.

In contrast, the PatientNullable class uses nullable types to overcome such problems. Here’s the equivalent relevant portion of the PatientNullable.getPatient() method:

   ...      contactperson = dataReader["ContactPerson"] ==          DBNull.Value ? null :          dataReader["ContactPerson"].ToString();      ssn = dataReader["SSN"] ==          DBNull.Value ? (long?)null :          Convert.ToInt64(dataReader["SSN"]);      billedamount = dataReader["BilledAmount"] ==          DBNull.Value ? (int?)null :          Convert.ToInt32(dataReader["BilledAmount"]);   ...                           

Note how the code ensures that nullable types used for properties containing null values get assigned without run-time exceptions, and without providing substitute values.

With those classes in place, Listing 4 shows the code for a simple console application (available with the downloadable code) that instantiates the PatientNullable and PatientNonNullable classes and calls the GetPatient() method of each class:

Checking the HasValue Property
When you run the sample application in Listing 4, you’ll see the output shown in Figure 2.

?
Figure 2. Sample Program Output: The output shows the difference in the values displayed in the SSN and BilledAmount properties in the PatientNullable and PatientNonNullable classes.

As Figure 2 shows, the PatientNonNullable class assigns a default (-1) value to the SSN property when the database contains a null value. In contrast, the PatientNullable class can accept and preserve the null value, showing clearly that the data is missing. Early .NET developers were forced to use extensive checks and assign specific values that represented null values in value types, but that’s no longer necessary in C# 2.0. Instead, you can use nullable types to avoid both the exceptions that occur when assigning null column values to value types, and the need to assign substitute values.

?
Figure 3. System.InvalidOperationException: Removing the HasValue check in the GetNullablePatient() method causes the framework to throw a System.InvalidOperationException.

In the GetNullablePatient() method in Listing 4, notice the HasValue check which determines whether the fields SSN and BilledAmount contain valid values. If the code had not performed the HasValue check, the framework would have thrown a System.InvalidOperationException error. Figure 3 illustrates how to create that error and shows the corresponding exception message.

Assigning Nullable Types to UI Elements
Assigning a nullable type to a property of a user-interface element requires the same sort of check. Here’s a modified version of the GetNullablePatient() method from a Windows Forms application that displays patient data like the console application:

   public void GetNullablePatient(int patientID)   {      patientNullable.GetPatient(patientID);         txtName.Text = patientNullable.Name.ToString();      txtAddress.Text = patientNullable.Address;         if (patientNullable.ContactPerson != null)         txtContact.Text = patientNullable.ContactPerson;      else         txtContact.Text = "No contact(s) available";         if (patientNullable.SSN.HasValue)      {         long ssn = (long)patientNullable.SSN;         txtSSN.Text = ssn.ToString();      }      else         txtSSN.Text = "0";         if (patientNullable.BilledAmount.HasValue)      {         int billedAmount = (int)patientNullable.BilledAmount;         txtBilledAmount.Text = patientNullable.BilledAmount;      }
?
Figure 4. Windows Forms Example: This application checks HasValue for the nullable ContactPerson, SSN, and BilledAmount fields, and when they're null, manually assigns a substitute value (0 in this case) for display in the controls.
else txtBilledAmount.Text = null; if(!patientNullable.BilledAmount.HasValue) patientNullable.BilledAmount = 0; } private void Form1_Load(object sender, EventArgs e) { GetNullablePatient(3); }

Note how the code assigns nullable types to the control properties, executing a HasValue check for each value that might be null. When you execute the windows application, the output is similar to Figure 4.

Databinding Controls to Nullable Types
Checking for null values manually is both tedious and error prone. Instead, you can use .NET 2.0 databinding to eliminate the manual check. Databinding controls to nullable types is slightly different than databinding to standard value types. You use the control’s DataBindings collection to assign bindings to control properties; in other words, by binding a specific property of a control to a specific property of the PatientNullable type, the application will be able to update the control property from the type, or (because the binding can be two-way) the type property from the control.

But standard controls don’t “understand” null values. One way to work around that problem is to create an extended TextBox class, adding code to perform null checks. Here’s the source code for a NullableTextBox class that extends the TextBox class. It provides a bindable Text property that overrides the Text property of the base class:

   public class NullableTextBoxControl : TextBox   {      [Bindable(true)]      public new int? Text      {         get         {            if (base.Text != String.Empty)               return new Nullable(int.Parse(base.Text));               return new Nullable(0);            }         set         {            if (value.HasValue == true)            {               base.Text = value.Value.ToString();            }            else            {               base.Text = null;            }         }      }   }
?
Figure 5. NullableTextBoxControl: This design-mode view of the sample form shows the NullableTextBoxControl in the Toolbox.

After you compile the code, Visual Studio adds the NullableTextBoxControl component to your ToolBox.

To use it, replace the txtBilledAmount TextBox control in the Windows Forms sample application with a NullableTextBoxControl named ntxtBilledAmount. Simply drag and drop this control to the form in its design view as shown in Figure 5.

To show the effect of the two-way binding, also add a Button control. When clicked, the button’s Click event code will display the current value of the BilledAmount property stored in the PatientNullable instance.

You need to change the GetNullablePatient() method in the application to reference the ntxtBilledAmount NullableTextBox control instead of the txtBilledAmount TextBox control. The rest of the method’s code remains the same. Here’s the altered code:

   public void GetNullablePatient(int patientID)   {      patientNullable.GetPatient(patientID);         txtName.Text = patientNullable.Name.ToString();      txtAddress.Text = patientNullable.Address;         if (patientNullable.ContactPerson != null)         txtContact.Text = patientNullable.ContactPerson;      else         txtContact.Text = "No contact(s) available";         if (patientNullable.SSN.HasValue)      {         long ssn = (long)patientNullable.SSN;         txtSSN.Text = ssn.ToString();      }      else         txtSSN.Text = "0";         if (patientNullable.BilledAmount.HasValue)      {         int billedAmount = (int)patientNullable.BilledAmount;         ntxtBilledAmount.Text = patientNullable.BilledAmount;      }      else         ntxtBilledAmount.Text = null;         if(!patientNullable.BilledAmount.HasValue)         patientNullable.BilledAmount = 0;   }

A binding ensures that any change in the NullableTextBoxControl will cause a corresponding change in the BilledAmount property’s value and vice versa. Add a binding to the DataBindings collection of the ntxtBilledAmount control to bind its Text property to the BilledAmount property of the PatientNullable class instance (called patientNullable in the following code):

   private void Form1_Load(object sender, EventArgs e)   {      GetNullablePatient(3);      this.ntxtBilledAmount.DataBindings.Add("Text",          patientNullable, "BilledAmount",true);   }

Finally, add the button Click event code to display the current BilledAmount value from the PatientNullable instance:

   private void btnClick_Click(      object sender, EventArgs e)   {      int billedAmount =          (int)patientNullable.BilledAmount;
?
Figure 6. Bound Control: When you change the value in the Billed Amount control, clicking the button shows that the BilledAmount value in the bound PatientNullable instance has changed as well.
MessageBox.Show( "The Billed Amount is: " + billedAmount.ToString(), "Patient Information"); }

Now you can compile and execute the application. The initial output is similar to Figure 4, but if you change the BilledAmount value in the TextBox and then click the Button shown beneath the TextBox controls you’ll see a screen like Figure 6.

Note that the BilledAmout property value (shown in the MessageBox in Figure 6) gets updated automatically when you change the contents of the “Billed Amount” NullableTextBoxControl.

By now, you should see the advantages of using nullable types in your C# applications. Using them not only simplifies your code, but also eliminates a common source of run-time errors when assigning values from database columns that can contain nulls.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist