Starting Simple
I really like the way Jon has started the book. This book is mostly for those who have already some experience with C# and want to know how things work under the hood. He starts by programming a simple hello world program which is mostly used in teaching e-commerce site design perhaps. The program contains a Product class. I really don't know if I would have permission to post code from the book here, so I guess it's even better practice if I come up with my own code . I would also add material I know here and there. I have also tried to show some design patterns used in the .Net framework as we get to each subject.
What Jon has tried to convey in the first chapter is how C# has evolved as a language from it's initial humble begining(version(1.0)) up to version(4.0). This chapter's aim is to merely impress the reader rather than educate. To show that how the language's evolution has benefited the programmer.
Okay so let's get right to it. Let's say we have a class "
Letter" as follows:
public class Letter
{
public string letterNo;
public DateTime letterDate;
public DateTime LetterDate
{
get { return letterDate; }
}
public string LetterNo
{
get { return letterNo; }
}
public Letter(string ltrNo, DateTime ltrDate)
{
this.letterNo = ltrNo;
this.letterDate = ltrDate;
}
public static ArrayList GetSampleLetters()
{
ArrayList list = new ArrayList();
list.Add(new Letter("1", DateTime.MinValue));
list.Add(new Letter("2", DateTime.MaxValue));
list.Add(new Letter("3", DateTime.Now));
return list;
}
public override string ToString()
{
return string.Format("Letter {0}({1})", letterNo, letterDate);
}
}
This class encapsulates the functionality of a letter class. There are some limitations here though:
- If we want to have a matching setter for our properties, it has to be public.
- The ArrayList class has no compile-time information about the object it contains.
- We have gone through a lot of code just to encapsulate two members, namely: letterNo and letterDate.
In C# 2.0 we can improve this a little. For example with C# 2.0 the concept of private setters is introduced and also one of the biggest improvements to C# in my opinion: Generics. So we can now change the program like so:
public class Letter
{
public string letterNo;
public DateTime letterDate;
public DateTime LetterDate
{
get { return letterDate; }
private set { letterDate = value; }
}
public string LetterNo
{
get { return letterNo; }
private set { letterNo = value; }
}
public Letter(string ltrNo, DateTime ltrDate)
{
LetterNo = ltrNo;
LetterDate = ltrDate;
}
public static List<Letter> GetSampleLetters()
{
List list = new List();
list.Add(new Letter("1", DateTime.MinValue));
list.Add(new Letter("2", DateTime.MaxValue));
list.Add(new Letter("3", DateTime.Now));
return list;
}
public override string ToString()
{
return string.Format("Letter {0}({1})", letterNo, letterDate);
}
}
As seen above, now we can use generics and private setters in our code. Generics help greatly here since we can now get compile errors if an object of unknown type is added to the list. Also, after fetching an element from the list we don't have to case it anymore since the elements are statically typed.
We still have a problem we have not addressed here and that is the use of abundant code to encapsulate the class members. C# 3.0 comes to our help. In C# 3.0 we can use automatic properties.
public class Letter
{
public DateTime LetterDate {get; private set;}
public string LetterNo { get; private set; }
public Letter() {}
public static List<Letter> GetSampleLetters()
{
return new List<Letter>()
{
new Letter {LetterNo = "1", LetterDate = DateTime.MinValue},
new Letter {LetterNo = "2", LetterDate = DateTime.MaxValue},
new Letter {LetterNo = "3", LetterDate = DateTime.Now}
};
}
public override string ToString()
{
return string.Format("Letter {0}({1})", LetterNo, LetterDate);
}
}
Now this is more like it. Now we have much less code with more functionality. Notice that we don't have the variables anymore which would force us to use properties everywhere adding to consistency. Also, the List initialization is also different here.
Now another subtle problem here is this: Although we have set a property to have a private setter and effectively made it read-only for the code outside the scope of the class we have not really made it read-only on the inside. Maybe it would be better to explicitly make it read-only. This can be done in C# 4.0 using the r"
readonly" keyword.
public class Letter
{
readonly DateTime LetterDate {get; private set;}
readonly string LetterNo { get; private set; }
public Letter() {}
public static List<Letter> GetSampleLetters()
{
return new List<Letter>()
{
new Letter {LetterNo = "1", LetterDate = DateTime.MinValue},
new Letter {LetterNo = "2", LetterDate = DateTime.MaxValue},
new Letter {LetterNo = "3", LetterDate = DateTime.Now}
};
}
public override string ToString()
{
return string.Format("Letter {0}({1})", LetterNo, LetterDate);
}
}
Sorting and Filtering
In the previous section we looked at the evolution of C# from an encapsulation point of view. But let's try another approach. If we want to sort our letters by the "LetterDate" property in C# 1.0 we would have to add another type that would implement the interface "IComparer" and then implement the Compare(object, object) method. Does this remind us of a known design pattern BTW? Yes, this is the
"Strategy Pattern" (for more known design patterns in .Net you can take a look at this
article in MSDN). With this pattern we have allowed more than one strategy for sorting the Array List. Let's say once we want to sort by the letter date and another time by the letter number ? The solution is to add two types which implement the IComparer interface and implement the compare method differently. As we see below:
public class LetterComparer : IComparer
{
public int Compare(object x, object y)
{
Letter first = (Letter)x;
Letter second = (Letter)y;
return first.LetterDate.CompareTo(second.LetterDate);
}
}
...
ArrayList letters = Letter.GetSampleLetters();
letters.Sort(new LetterComparer());
foreach(Letter letter in letters)
Console.WriteLine(letter.ToString());
There are however a couple of things we don't like with this setup. Firstly we have to add an extra type for each new strategy that we have. Second, we see a lot of casts. Some explicit as in the Compare method and some implicit as in the foreach for the print out. Not only is that a performance issue, it is also the presumtion that we are always passing only Letters to the method. You may say that we can check foreach call to the method. But isn't that another overhead? Fortunately, C# 2.0 comes to our rescue:
public class LetterComparer : IComparer<Letter>
{
public int Compare(Letter x, Letter y)
{
return x.LetterDate.CompareTo(y.LetterDate);
}
}
...
List<Letter> letters = Letter.GetSampleLetters();
letters.Sort(new LetterComparer());
foreach(Letter letter in letters)
Console.WriteLine(letter.ToString());
So we have fixed the ArrayList problem. But we are still creating a new type for a simple task of comparing a member. C# 2.0 solves this problem with "Anonymous Methods":
List<Letter> letters = Letter.GetSampleLetters();
letters.Sort(delegate(Letter x, Letter y){ return x.LetterDate.CompareTo(y.LetterDate)}; );
foreach(Letter letter in letters)
Console.WriteLine(letter.ToString());
Wow now that's a lot of change. Notice here that this is not always necessarily the right thing to do. If the class requires a more complex approach for object comparison then we would definitely stick with the Strategy Pattern.
Here we have made the code quite compact. But can C# 3.0 do better ? Of course it can...With the use of lambda expressions:
List<Letter> letters = Letter.GetSampleLetters();
letters.Sort((x,y)=> x.LetterDate.CompareTo(y.LetterDate));
foreach(Letter letter in letters)
Console.WriteLine(letter.ToString());
The section bolded shows a "lambda expression". This still creates a delegate. But this time we haven't really specified the type of the parameters of the delegate. As it turns out C# 3.0 can even make this much easier by using "extension methods":
List<Letter> letters = Letter.GetSampleLetters();
foreach(Letter letter in letters.OrderBy(p => p.LetterDate)
Console.WriteLine(letter.ToString());
In the above snippet we are calling a method on the letters object which actually does not exist in the List<T> object members. We'll talk about extension methods later. This sorting operation is not even in-place. We are sorting the elements and then printing them out the List remains unsorted.
Querying Collections
If we were to query a collection and find all letters with the letterNo property higher than a certain value, this is how we would have done it in C# 1.0
ArrayList Letters = Letter.GetSampleLetters();
foreach(Letter letter in Letters)
if(letter.LetterNo > 20)
Console.WriteLine(letter.ToString());
Here is how we can do the same task with C# 2.0:
List letters = Letter.GetSampleLetters();
Predicate test = delegate(Letter l){return l.letterDate > 20;};
List matches = letters.FindAll(test);
Action print = Console.WriteLine;
matches.ForEach(print);
This is by no means less complicated than what we could do in C# 1.0. Actually the code written by C# 1.0 is much more readable. Although what we have written in C# 2.0 is almost English ! What we have to realize here is not the number of lines of code we have written but the power we have over the operations. Now we are able to have the action and the predicate in variables. This immediately triggers a possible use of the
"Template Method Pattern" in my head.
This pattern is really close to the Strategy Pattern with the difference that in the latter, the algorithms are usually radically different than one another. Whereas in the former, the sub-classes all have the same structure as the parent algorithm with some steps overridden in each child. The Template Pattern is also known for "fill in the blanks pattern". In this case for example the sub-classes could each override methods like "Predicate
GetPredicate()" and "Predicate GetAction()" and effectively change the action and the predicate on what is being done in the collection.
The above code can of course be written even in a single line which is probably due to the use of the
"Decorator Pattern":
Letter.GetSampleLetters().FindAll(delegate(Letter l){return l.letterDate > 20;}).ForEach(Console.WriteLine);
There is still some as Joe puts it "fluf" around the definition of the delegate. Indeed we can even make the code shorter by the use of lambda expressions.
foreach(Letter letter in Letter.GetSampleLetters().Where(l => l.letterNo > 20))
Console.WriteLine(l);
To recap, what we have done in C# 2.0 has effectively improved the separation of concern issue by decoupling the predicate and the action. With the help of C# 3.0 we were able to further shorten our code and make it more readable. C# 4.0 does not give us any added benefits in this area.
Handling missing data
In C# 1.0 there where three possible options to handle missing data. Let's say in our Letter class some letters are still waiting to be numbered and they don't have a letterNo yet. But can we set the DateTime type to null ? No. That is not a nullable type. The three solutions were to either add a magic number to the field(DateTime.MinValue()), Wrap it in a sentinel object or add a boolean field to check if the letterNo has been inserted or not. None of these seem straightforward methods. In C# 2.0 Nullable<T> structure is introduced. This structure coupled with the syntactic goodness shown below gives us the flexibility:
int? number;
public int? number
{
get{ return number; }
private set{ number = value; }
}
Optional Parameters and Default Values in C# 4.0
So far we have not talk much about C# 4.0 . A new feature added to this version of C# which in my opinion is long overdue. Is the use of default values for the parameters and also optional parameters for methods:
public Letter(int? LetterNo = null, DateTime letterDate)
{
this.letterNo = letterNo;
this.letterDate = letterDate;
}
Introducing LINQ
LINQ or Language Integrated Query is all C# 3.0 is all about. C# 2.0 introduced generics and was basically fixing up on the inaccuracies of it's ancestor. With C# 3.0 a very powerful method of querying on different data sources was introduced. The idea is to use a common language or expressive query language that could be used to talk to any kind of data source. From a database and collection of objects to XML and COM interoperability.
We have already worked with some aspects of LINQ but we have not shown the actual "query expressions". For example if we want to write the same code for finding letters with the letterNo > 20 with an explicit query expression it would look like this:
List letters = Letter.GetSampleLetters();
var result = from Letter l in letters
where l.LetterNo > 20
select l;
foreach (Letter l in result)
Console.WriteLine(l.ToString());
As you can see in the above code, the expression looks very similar to an SQL statement. In the data-driven world of today most developers already know SQL. The choice of a SQL approach to the LINQ syntax then, IMO is an apt one. One might think why we have gone to such lengths to find a letter with a certain letterNo where we could just basically iterate through the list or another might take issue with the performance of pulling down the data from the database wrapping it up into objects and then querying the object. Although the former issue is a valid one and a statement like the above makes no sense in a real life scenario. The latter issue can't be any further from the truth. In the case of LINQ to SQL, LINQ knows not to do just that. The above query expression is going to be translated into a SQL statement and is going to be executed directly on the database.
So far we have talked about querying collections of objects and querying databases. We can also use LINQ to query XML documents. For example if our data is stored as an XML document, we would be able to use an expression very similar to the one above to search for data. We can write our own providers for LINQ. All these and other features that make LINQ a very flexible feature will be discussed later on.