Friday 17 December 2010

C# object initializers: use them smart!

Object initializers came with .Net framework in 2008. Using object initializers we can easily initialize our objects fields without writing many constructors:

            // Using object initializer
            Person demoP = new Person()
            {
                ID = 0,
                Name = "Happy",
                Age = 1
            };


As we can see, this method is simple. As in the old way, we can write a constructor or set fields by hand:

            Person oldDemoP = new Person();
            oldDemoP.ID = 0;
            oldDemoP.Name = "Happy";
            oldDemoP.Age = 1;



Using object initializers, we are able to save a lot of coding. That's good, isn't it? Less coding means less work and more beer, so we happy, aren't we?

Sorry, but the answer is: Not at every cases. In the real life, we spend our work hours with creating new lines of codes only about 10% and we are debugging our code all day from morning to night. And in real life, things don't work. You have databases and objects with bad design or with good design and bad data. You think every person has Age but after 3 migrates you can find some records that don't have.
Let's take a simple object (Person):

    public class Person
    {
        public int ID { getset; }
        public string Name { getset; }
        public int Age { getset; }
    }

Person has ID, Name and Age properties.

In real life scenarios there are many case when you know that, you can get _Always_ the age of the person and at two case in the 1.5 million records you won't get it. In this way you get Exception when try assign a null value to an Age property. The question is: How does the Exception look like? What we can find in the error logs?

Presenting the problem I made a simple example. I simulated my database with a list and use a special person class with object as property types:

    public class PersonDS
    {
        public object ID { getset; }
        public object Name { getset; }
        public object Age { getset; }
    }


I used this type for making an emulated simple sample database:

            List<PersonDS> database = new List<PersonDS>();
 
            database.Add(new PersonDS() { ID = 1, Name = "Peter", Age = 18 });
            database.Add(new PersonDS() { ID = 2, Name = "Liza", Age = null });
            database.Add(new PersonDS() { ID = 3, Name = "Cloe", Age = 16 });


I. In the old way, we can set values one by one. Now, we get error at the assignment:


 // Old way to read datas
            try
            {
                List<Person> oldPersons = new List<Person>();
                foreach (var datarow in database)
                {
                    Person p = new Person();
                    p.ID = (datarow.ID as int?).Value;
                    p.Name = datarow.Name as string;
                    p.Age = (datarow.Age as int?).Value;// Throws Exception at value assignment
                }
            }
            catch (Exception exc)
            {
                Console.WriteLine(string.Format("Message: {1}\nStackTrace: {1}"
                    , exc.Message, exc.StackTrace));
            }


II. Using the new way, we get the Exception when initializing the object:

            // The new way to read datas
            try
            {
                List<Person> newPersons =   
                        (from p in database
                         select new Person()
                         {// Throws Exception at object initialize
                             ID = (p.ID as int?).Value,
                             Name = p.Name as string,
                             Age = (p.Age as int?).Value
                         }).ToList();
            }
            catch (Exception exc)
            {
                Console.WriteLine(string.Format("Message: {1}\nStackTrace: {1}"
                    , exc.Message, exc.StackTrace));
            }
        }
As we can see, we get our exceptions in both cases. But at the old way, we can see that we have a missing Age value at the first look. With object initializers, we see only that we got an error at the initialize. And we have to find out what was the error exactly.
We can find many problems with this example: at the real life, I never used this form to read data from database: p.Age = (datarow.Age as int?).Value;// Throws Exception at value assignment
There are many ways to do this properly but the target is showing a simple problem with object initializers: error messages are not enough well detailed. In real life we usually don't have objects with only three properties. And we can get data with many ways: databases, xmls, streams, user input, ... 
The conclusion is: let's type a bit more but make error safe code. When we should bet our month salary for that property and want to say: 'Oh, it must be filled', don't be lazy and handle the possibility when it's not and we can sit down and open a beer.


Morzel

The entire code example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ObjectInitializersVSOldWays
{
    public class Person
    {
        public int ID { getset; }
        public string Name { getset; }
        public int Age { getset; }
    }
 
    public class PersonDS
    {
        public object ID { getset; }
        public object Name { getset; }
        public object Age { getset; }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            // Using object initializer
            Person demoP = new Person()
            {
                ID = 0,
                Name = "Happy",
                Age = 1
            };
 
            Person oldDemoP = new Person();
            oldDemoP.ID = 0;
            oldDemoP.Name = "Happy";
            oldDemoP.Age = 1;
 
            List<PersonDS> database = new List<PersonDS>();
 
            database.Add(new PersonDS() { ID = 1, Name = "Peter", Age = 18 });
            database.Add(new PersonDS() { ID = 2, Name = "Liza", Age = null });
            database.Add(new PersonDS() { ID = 3, Name = "Cloe", Age = 16 });
 
            // Old way to read datas
            try
            {
                List<Person> oldPersons = new List<Person>();
                foreach (var datarow in database)
                {
                    Person p = new Person();
                    p.ID = (datarow.ID as int?).Value;
                    p.Name = datarow.Name as string;
                    p.Age = (datarow.Age as int?).Value;// Throws Exception at value assignment
                }
            }
            catch (Exception exc)
            {
                Console.WriteLine(string.Format("Message: {1}\nStackTrace: {1}"
                    , exc.Message, exc.StackTrace));
            }
 
            // The new way to read datas
            try
            {
                List<Person> newPersons =   
                        (from p in database
                         select new Person()
                         {   // Throws Exception at object initialize
                             ID = (p.ID as int?).Value,
                             Name = p.Name as string,
                             Age = (p.Age as int?).Value
                         }).ToList();
            }
            catch (Exception exc)
            {
                Console.WriteLine(string.Format("Message: {1}\nStackTrace: {1}"
                    , exc.Message, exc.StackTrace));
            }
        }
    }
}
And the output is:
Message:    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Nullable`1.get_Value()
   at ObjectInitializersVSOldWays.Program.Main(String[] args) in D:\--- Souce ---\Blog\ObjectInitializersVSOldWays\ObjectInitializersVSOldWays\Program.cs:line 41
StackTrace:    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Nullable`1.get_Value()
   at ObjectInitializersVSOldWays.Program.Main(String[] args) in D:\--- Souce ---\Blog\ObjectInitializersVSOldWays\ObjectInitializersVSOldWays\Program.cs:line 41
Message:    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Nullable`1.get_Value()
   at ObjectInitializersVSOldWays.Program.<Main>b__4(PersonDS p) in D:\--- Souce ---\Blog\ObjectInitializersVSOldWays\ObjectInitializersVSOldWays\Program.cs:line 54
   at System.Linq.Enumerable.WhereSelectListIterator`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at ObjectInitializersVSOldWays.Program.Main(String[] args) in D:\--- Souce ---\Blog\ObjectInitializersVSOldWays\ObjectInitializersVSOldWays\Program.cs:line 52
StackTrace:    at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Nullable`1.get_Value()
   at ObjectInitializersVSOldWays.Program.<Main>b__4(PersonDS p) in D:\--- Souce ---\Blog\ObjectInitializersVSOldWays\ObjectInitializersVSOldWays\Program.cs:line 54
   at System.Linq.Enumerable.WhereSelectListIterator`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at ObjectInitializersVSOldWays.Program.Main(String[] args) in D:\--- Souce ---\Blog\ObjectInitializersVSOldWays\ObjectInitializersVSOldWays\Program.cs:line 52

No comments:

Post a Comment