Sunday, July 26, 2009

VB.NET Default Form Instances

What is a default form instance?

VB.NET is a fully object oriented language so, as such, basically everything you use is an object. Each object is an instance of its type, e.g. a String object is an instance of the String class. Creating an instance of a type is also known as instantiating that type. When dealing with classes, with the exception of the String class, the only way to instatntiate a type is to invoke a constructor, which is done with the New keyword:

Dim someVariable As SomeType = New SomeType

That code declares a variable whose type is SomeType, creates an instance of the SomeType type and then assigns the object to the variable. VB.NET also supports the following abbreviated syntax, which is functionally equivalent:

Dim someVariable As New SomeType

In your code, you may use various means to get new objects, e.g. some classes may have a Create method that returns a new instance, but all such methods will be using constructors internally. Forms are a special case in VB.NET as of VB 2005, which introduced the concept of default instances.

Every time you add a new form to your project you are creating a new class that inherits the System.Windows.Forms.Form class. Like other classes, forms have constructors and you can create an instance by invoking a constructor, but you can also use the class’s default instance. The default instance is an object of that type that the VB application framework creates and manages for you. For instance, conventionally you would do this to show a form:

Dim myForm As New SomeForm
 
myForm.Show()

If you use the default instance then you don’t need to invoke a constructor explicitly. You simply access the default instance directly via the My.Forms object:

My.Forms.SomeForm.Show()

or just using the class name:

SomeForm.Show()

It should be noted that, once compiled, this code will still cause a constructor to be invoked in order to create the default instance. The significance of this will be explained later.

One of the main goals of VB has always been to provide power while make programming as easy as possible for as many people as possible. The introduction of default form instances is in furtherance of that goal. Many people new to OOP use objects in VB.NET but they don’t really comprehend them properly. As such, they can have a great deal of difficulty dealing with forms as objects and making forms interact with each other. This is also true of some more-experienced VB6 developers who have made the move to VB.NET. VB.NET default instances behave like VB6 default instances so they feel familiar to VB6 developers making the move to VB.NET.

When should you use default instances?

If you’re going to use default instances at all then you should use them pretty much all the time. I’ll qualify that statement later but, for the moment, let’s say that you should use them all the time or not at all.

One important point to note is that, if you leave the application framework enabled when you create a Windows Forms Application project, your startup form is the default instance of its type. If you don’t know what the application framework is then you can safely assume that it’s enabled and not worry about it.

The fact that your startup form is a default instance is quite useful. For instance, let’s say that you are displaying multiple records in a DataGridView in the main form and you open a new form for the user to edit one of those records. Once the editing is done, you want to update the main form with the new data before closing the dialogue. How do you do it? This is the sort of thing that causes great confusion to those new to OOP and VB.NET. In .NET OO terms, the dialogue needs a reference to the main form in order to update it. Providing that reference is actually not too difficult but the mechanism is not immediately obvious to many. With default instances it’s easy. The dialogue form can access the default instance of the main form’s type using the class name, so it can access the main form, e.g.

Private Sub okButton_Click(ByVal sender As Object, _
                           ByVal e As EventArgs) Handles okButton.Click
    With Form1.DataGridView1.CurrentRow.Cells
        .Item(0).Value = Me.TextBox1.Text
        .Item(1).Value = Me.TextBox2.Text
    End With
 
    Me.Close()
End Sub

In this case, when the user clicks the OK button, the dialogue will directly access the grid on the main form and update the cells of the current row.

Another example of where default instances are helpful is in a multiple-document interface (MDI) application. The child forms in an MDI application are not inherently aware of each other. Let’s say that your MDI application consists of a parent form and two child forms. Let’s say that making a change on one child form needs to cause a change on the other child form. If the children don’t inherently know about each other, how can this be done? The answer is, again, that the first child form simply references the default instance of the second child form’s type via the class name. This will allow changes to be made to the second child form, assuming that it is the default instance of its type.

That last sentence touches on an important point and a source of confusion for some. There’s no point updating the default instance of a form class if you haven’t actually displayed the default instance of that class in the first place. This is one of the reasons I say that, if you’re going to use default instances at all, you should use them all the time. For instance, if you create a form and display it like this:

Dim myForm As New SomeForm
 
myForm.Show()

then you can’t then refer to the default instance of the SomeForm class and expect the form on your screen to be affected. You’ve created one instance and displayed that, then the system creates the default instance. They are two different objects so making changes to one will not affect the other. If you’re going to make changes by referring to the default instance then you need to have displayed the default instance in the first place:

SomeForm.Show()

Now, default instances don’t hold much appeal for the experienced VB.NET developer. The situations I’ve used as examples up to now should never occur in a well-designed application. For instance, the first example involves a dialogue updating a DataGridView on the main form. In a well-designed application the dialogue shouldn’t have to even know that the main form exists. The dialogue should simply make the new data available via public properties and close. The main form would then retrieve the data itself and update its own UI. In the second example, where a change on one MDI child form causes a change in another child form, the child forms should, again, not have to have knowledge of each other. In a well-designed application the first child would raise an event that can be handled by the parent form, which can then update the other child. The parent form created both the children so it has to have knowledge of both, so it should have no problem accessing both.

There is one situation though, where using the default instance can provide a small benefit to the experienced developer. Because there is always one and only one default instance, they can be useful where you want your form to exhibit singleton-like behaviour. For instance, let’s say that you have a menu item that is supposed to display a form. If the form isn’t already open you want it to be displayed but, if it is already open, you want the existing form to receive focus. Normally you would have to retain a reference to the form when you open it and then, when the menu items is clicked, you need to check whether there is an existing form and, if there is, whether it has already been disposed:

Private toolWindow As SomeForm
 
Private Sub OpenToolWindowToolStripMenuItem_Click(ByVal sender As Object, _
                                                  ByVal e As EventArgs) _
Handles OpenToolWindowToolStripMenuItem.Click
    If Me.toolWindow Is Nothing OrElse _
       Me.toolWindow.IsDisposed Then
        'The tool window has not been opened or it has been opened and closed.
        Me.toolWindow = New SomeForm
        Me.toolWindow.Show()
    Else
        'A Tool window is currently open.
        Me.toolWindow.Activate()
    End If
End Sub

Using the default instance, that code simplifies to this:

Private Sub OpenToolWindowToolStripMenuItem_Click(ByVal sender As Object, _
                                                  ByVal e As EventArgs) _
Handles OpenToolWindowToolStripMenuItem.Click
    'Make sure the tool window is displayed.
    SomeForm.Show()
 
    'Make sure the tool window has focus.
    SomeForm.Activate()
End Sub

That does neaten the code a bit, although it’s not a huge improvement, especially if you need to handle events of the form and/or access its methods and/or properties. Personally, I’d just stick with the first style of code but it’s good to know your options.

When can’t you use default instances?

I said earlier that, if you’re going to use default instances at all, you should use them all the time. That is generally true but there are certain situations where you can’t use them. For less experienced developers particularly, these situations will be very much in the minority and may not even be encountered at all in many projects, but it’s important to be aware of them.

1. You need to display multiple instances of the same form class simultaneously.

An example of this would be an MDI application where multiple documents are opened at the same time, all using the same form class. I said earlier that there is always one and only one default instance so, obviously, that presents a problem if you need multiple instances open simultaneously. If you’re going to have to create one or more instances explicitly then you should create them all explicitly. Consistency is a good thing and, if all instances are equivalent, they should be treated in exactly the same way.

2. You need to pass data to the form when you create it.

When you access the default instance for the first time the system must create it and to do that it must invoke a constructor. Specifically, it invokes the constructor that has no parameters. If you want to create a form by invoking a constructor with parameters then you must do so explicitly. It’s also worth noting that if you remove the parameterless constructor and leave only one or more constructors with parameters then your form class will have no default instance. Trying to access it in that case will cause a compilation error.

3. You need to access a form from a secondary thread.

In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread. As such, if you test the InvokeRequired property of the default instance you will always be accessing the default instance for the current thread, not the one that was displayed on the main thread.

While experienced .NET developers will probably never feel the need to use default instances, those new to OOP or VB.NET may find them useful. They do have their pitfalls though so, like anything, it’s good to understand what you’re dealing with.

Monday, July 6, 2009

Database-independent Data Access Layer

We all know that well-written applications separate the various areas of responsibility into multiple layers, right? Generally speaking, the less each of those layers depend on specific functionality of the others the better. One of those areas of responsibility that warrants separation is data access. There are numerous tools about to generate data access code for you these days but, even if you're more of a roll-your-own kinda person, there's still help at hand. ADO.NET provides the tools to write data access code that is independent of the underlying data source and it has done since version 2.0.

The lynchpin of this functionality is the DbProviderFactory class. It provides methods to generate instances of all the usual ADO.NET objects, e.g. connections, commands and data adapters, for any specific ADO.NET provider.

Let’s start with a whirlwind tour of the System.Data and System.Data.Common namespaces. Amongst other things, the System.Data namespace contains the interfaces that declare the set of functionality required by ADO.NET data access objects, e.g. IDataReader for all data readers and IDbConnection for all connections. The System.Data.Common namespace contains classes that provide a basic concrete implementation of these interfaces, e.g. DbDataReader implements IDataReader and DbConnection implements IDbConnection. Each data source-specific ADO.NET provider will then declare a set of classes that inherit those from System.Data.Common to provide a data source-specific set of functionality, e.g. SqlClient.SqlDataReader inherits DbDataReader and OleDb.OleDbConnection inherits DbConnection.

The System.Data.Common namespace also includes the DbProviderFactory class, which provides an abstract implementation of an ADO.NET object factory. Each data source-specific ADO.NET provider should inherit this class and provide its own data source-specific implementation, e.g. SqlClientFactory and OleDbFactory. Third party ADO.NET providers can and should do the same thing, e.g. MySql.Data.MySqlClient.MySqlClientFactory.

Now, ADO.NET factory classes are singletons. Each concrete factory implementation will have a static/Shared Instance field that you can use to get the one and only instance of that type. Doing so directly defeats the purpose somewhat though. You will normally get a DbProviderFactory instance using the DbProviderFactories helper class, which is also a member of the System.Data.Common namespace. The DbProviderFactories class includes two static/Shared methods: GetFactoryClasses will return a DataTable containing information about all ADO.NET factories available and GetFactory will return an instance of a specific ADO.NET factory class.

To see how this works, try creating a new Windows Forms application, adding a DataGridView to the default form and then binding that grid to the result of the DbProviderFactories.GetFactoryClasses method:

C#

this.dataGridView1.DataSource = DbProviderFactories.GetFactoryClasses();

VB

Me.DataGridView1.DataSource = DbProviderFactories.GetFactoryClasses()

If you run that application you will see a row each for factories named “Odbc Data Provider”, “OleDb Data Provider”, “OracleClient Data Provider” and “SqlClient Data Provider”. If you’ve installed Microsoft SQL Server CE then you’ll also see a row for a factory named “Microsoft SQL Server Compact Data Provider”. If you’ve installed some third party data provider then you’ll see a row for that too. For instance, if you’ve installed MySQL’s Connector/Net then you’ll see a row for a factory named “MySQL Data Provider”. If you’ve installed Oracle’s ODAC then you’ll see a row for a factory named “Oracle Data Provider for .NET”.

To get an appropriate instance of the DbProviderFactory class for your data source you would normally call the DbProviderFactories.GetFactory method. You can pass that method either a DataRow from the DataTable returned by the GetFactoryClasses method or else one of the values from the InvariantName column of that table. For instance, you could pass the string “System.Data.SqlClient” to the GetFactory method and it would return the value of the SqlClientFactory.Instance field.

This provides us with two simple and logical ways to create an ADO.NET factory. Firstly, we can call GetFactories and display the results to the user, e.g. in a ComboBox, for them to choose a provider:

C#

this.comboBox1.DisplayMember = "Name";
this.comboBox1.DataSource = DbProviderFactories.GetFactoryClasses();

VB

Me.ComboBox1.DisplayMember = "Name"
Me.ComboBox1.DataSource = DbProviderFactories.GetFactoryClasses()

We can then call GetFactory and pass the row they selected as an argument:

C#

DataRow providerRow = ((DataRowView)this.comboBox1.SelectedItem).Row;
DbProviderFactory factory = DbProviderFactories.GetFactory(providerRow);

VB

Dim providerRow As DataRow = DirectCast(Me.ComboBox1.SelectedItem, DataRowView).Row
Dim factory As DbProviderFactory = DbProviderFactories.GetFactory(providerRow)

Alternatively, we can store the invariant name of our desired provider somewhere, e.g. in the config file, and pass that to GetFactory as an argument:

C#

string invariantName = Properties.Settings.Default.ProviderFactoryInvariantName;
DbProviderFactory factory = DbProviderFactories.GetFactory(invariantName);

VB

Dim invariantName As String = My.Settings.ProviderFactoryInvariantName
Dim factory As DbProviderFactory = DbProviderFactories.GetFactory(invariantName)

So, once we have our factory object, what do we do with it? Well, we use it to create all our data access objects, including connections, commands, parameters, data adapters and more. For instance, the following code might form part of your repository layer:

C#

private readonly DbProviderFactory _factory;
 
public string ConnectionString { get; set; }
 
public Repository(string factoryName)
{
    this._factory = DbProviderFactories.GetFactory(factoryName);
}
 
public Repository(DataRow factoryRow)
{
    this._factory = DbProviderFactories.GetFactory(factoryRow);
}
 
public DbConnection GetConnection()
{
    return this.GetConnection(this.ConnectionString);
}
 
public DbConnection GetConnection(string connectionString)
{
    DbConnection connection = this._factory.CreateConnection();
 
    connection.ConnectionString = connectionString;
 
    return connection;
}
 
public DbParameter GetParameter()
{
    return this._factory.CreateParameter();
}
 
public DbParameter GetParameter(string parameterName,
                                object value)
{
    DbParameter parameter = this.GetParameter();
 
    parameter.ParameterName = parameterName;
    parameter.Value = value;
 
    return parameter;
}
 
public DbCommand GetCommand()
{
    return this._factory.CreateCommand();
}
 
public DbCommand GetCommand(string commandText)
{
    DbCommand command = this.GetCommand();
 
    command.CommandText = commandText;
 
    return command;
}
 
public DbCommand GetCommand(string commandText,
                            IDictionary parameters)
{
    DbCommand command = this.GetCommand(commandText);
 
    foreach (string parameterName in parameters.Keys)
    {
        command.Parameters.Add(this.GetParameter(parameterName,
                                                 parameters[parameterName]));
    }
 
    return command;
}
 
public DataTable GetDataTable(string procName,
                              IDictionary parameters)
{
    DbCommand command = this.GetCommand(procName,
                                        parameters);
    DbConnection connection = this.GetConnection();
 
    command.Connection = connection;
    command.CommandType = CommandType.StoredProcedure;
 
    connection.Open();
 
    DbDataReader reader = command.ExecuteReader(CommandBehavior.KeyInfo |
                                                CommandBehavior.CloseConnection);
    DataTable table = new DataTable();
 
    table.Load(reader);
    reader.Close();
 
    return table;
}

VB

Private ReadOnly _factory As DbProviderFactory
Private _connectionString As String
 
Public Property ConnectionString() As String
    Get
        Return Me._connectionString
    End Get
    Set(ByVal value As String)
        Me._connectionString = value
    End Set
End Property
 
Public Sub New(ByVal factoryName As String)
    Me._factory = DbProviderFactories.GetFactory(factoryName)
End Sub
 
Public Sub New(ByVal factoryRow As DataRow)
    Me._factory = DbProviderFactories.GetFactory(factoryRow)
End Sub
 
Public Function GetConnection() As DbConnection
    Return Me.GetConnection(Me.ConnectionString)
End Function
 
Public Function GetConnection(ByVal connectionString As String) As DbConnection
    Dim connection As DbConnection = Me._factory.CreateConnection()
 
    connection.ConnectionString = connectionString
 
    Return connection
End Function
 
Public Function GetParameter() As DbParameter
    Return Me._factory.CreateParameter()
End Function
 
Public Function GetParameter(ByVal parameterName As String, _
                             ByVal value As Object) As DbParameter
    Dim parameter As DbParameter = Me.GetParameter()
 
    parameter.ParameterName = parameterName
    parameter.Value = value
 
    Return parameter
End Function
 
Public Function GetCommand() As DbCommand
    Return Me._factory.CreateCommand()
End Function
 
Public Function GetCommand(ByVal commandText As String) As DbCommand
    Dim command As DbCommand = Me.GetCommand()
 
    command.CommandText = commandText
 
    Return command
End Function
 
Public Function GetCommand( _
ByVal commandText As String, _
ByVal parameters As IDictionary(Of String, Object)) As DbCommand
    Dim command As DbCommand = Me.GetCommand(commandText)
 
    For Each parameterName As String In parameters.Keys
        command.Parameters.Add(Me.GetParameter(parameterName, _
                                               parameters(parameterName)))
    Next parameterName
 
    Return command
End Function
 
Public Function GetDataTable( _
ByVal procedureName As String, _
ByVal parameters As IDictionary(Of String, Object)) As DataTable
    Dim command As DbCommand = Me.GetCommand(procedureName, parameters)
    Dim connection As DbConnection = Me.GetConnection()
 
    command.Connection = connection
    command.CommandType = CommandType.StoredProcedure
 
    connection.Open()
 
    Dim reader As DbDataReader = command.ExecuteReader(CommandBehavior.KeyInfo Or _
                                                       CommandBehavior.CloseConnection)
    Dim table As New DataTable()
 
    table.Load(reader)
    reader.Close()
 
    Return table
End Function

The GetDateTable method takes the name of a stored procedure and a dictionary of parameter values keyed on name as arguments. It then invokes other methods that use the factory to create a connection and a command with parameters. The DbProviderFactory class can also generate connection string builders, data adapters and command builders, so you can implement many other scenarios in a similar fashion. In this way you can build up a complete data access layer with nary a reference to any specific data source.

Thursday, July 2, 2009

Sorting Arrays and Collections (Part 3)

Part 1 here
Part 2 here

In part 1 of this series we looked at using the IComparable interface to provide sorting by allowing objects to compare themselves to each other. In part 2 we looked at using the IComparer interface to provide custom sorting through the external comparison of objects that may or may not be inherently comparable. In this third and final part we look at using the Comparison(T) delegate to provide similar functionality to the IComparer interface but without the need to define a whole class. The Comparison delegate allows us to use a method declared anywhere to perform the comparisons. Most often that would be in the same code file as we’re performing a one-off sort.

The signature of the Comparison delegate should put you in mind of the methods we’ve been using for comparisons already. It takes two arguments of type T and returns an Int32 value that indicates their relative order. That’s exactly the same as the IComparer.Compare method. Not surprisingly, the implementation of the method referred to by this delegate should be exactly the same as the implementation of an equivalent IComparer.Compare method.

Going back to our example from part 2, where we were able to sort a list of Person objects by both LastName and FirstName, we can basically extract the implementations of the CompareByFirstName and CompareByLastName methods. Instead of declaring them in a separate class that implements the IComparer interface we can declare them right there in the same class that contains the sorting code:

C#

private int ComparePeopleByFirstName(Person x, Person y)
{
    // Compare by FirstName by default.
    int result = x.FirstName.CompareTo(y.FirstName);
 
    // If FirstNames are the same...
    if (result == 0)
    {
        // ...compare by LastName.
        result = x.LastName.CompareTo(y.LastName);
    }
 
    return result;
}
 
private int ComparePeopleByLastName(Person x, Person y)
{
    // Compare by LastName by default.
    int result = x.LastName.CompareTo(y.LastName);
 
    // If LastNames are the same...
    if (result == 0)
    {
        // ...compare by FirstName.
        result = x.FirstName.CompareTo(y.FirstName);
    }
 
    return result;
}

VB

Private Function ComparePeopleByFirstName(ByVal x As Person, _
                                          ByVal y As Person) As Integer
    'Compare by FirstName by default.
    Dim result As Integer = x.FirstName.CompareTo(y.FirstName)
 
    'If FirstNames are the same...
    If result = 0 Then
        '...compare by LastName.
        result = x.LastName.CompareTo(y.LastName)
    End If
 
    Return result
End Function
 
Private Function ComparePeopleByLastName(ByVal x As Person, _
                                         ByVal y As Person) As Integer
    'Compare by LastName by default.
    Dim result As Integer = x.LastName.CompareTo(y.LastName)
 
    'If LastNames are the same...
    If result = 0 Then
        '...compare by FirstName.
        result = x.FirstName.CompareTo(y.FirstName)
    End If
 
    Return result
End Function

We can then create an instance of the Comparison delegate that references one or other of those methods to sort by the appropriate properties:

C#

List<Person> people = new List<Person>();
 
people.Add(new Person("Mary", "Smith"));
people.Add(new Person("John", "Williams"));
people.Add(new Person("John", "Smith"));
people.Add(new Person("Andrew", "Baxter"));
 
Console.WriteLine("Before sorting:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new Comparison<Person>(ComparePeopleByLastName));
 
Console.WriteLine("After sorting by LastName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new Comparison<Person>(ComparePeopleByFirstName));
 
Console.WriteLine("After sorting by FirstName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0} {1}",
                                    person.FirstName,
                                    person.LastName));
}

VB

Dim people As New List(Of Person)
 
people.Add(New Person("Mary", "Smith"))
people.Add(New Person("John", "Williams"))
people.Add(New Person("John", "Smith"))
people.Add(New Person("Andrew", "Baxter"))
 
Console.WriteLine("Before sorting:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New Comparison(Of Person)(AddressOf ComparePeopleByLastName))
 
Console.WriteLine("After sorting by LastName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New Comparison(Of Person)(AddressOf ComparePeopleByFirstName))
 
Console.WriteLine("After sorting by FirstName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0} {1}", _
                                    person.FirstName, _
                                    person.LastName))
Next

Removing the need to declare a whole new class makes the Comparison delegate more convenient than the IComparer interface in many cases, but it can be more convenient still if we employ anonymous methods or lambda expressions.

C# 2.0 introduced the notion of anonymous methods. Anonymous methods can be used to initialise a delegate just as a named method can. If the code to perform our comparisons is relatively simple then using an anonymous method to create our Comparison delegate is simpler than writing a separate named method. Going back to our example from part 2, where we sorted strings by length instead of alphabetically, we could rewrite that code as follows:

C#

List<string> strings = new List<string>();
 
strings.Add("The longest of all");
strings.Add("Short");
strings.Add("Even longer");
strings.Add("Longer");
 
Console.WriteLine("Before sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}
 
strings.Sort(delegate(string x,
                      string y)
{
    return x.Length.CompareTo(y.Length);
});
 
Console.WriteLine("After sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}

Our anonymous method has the appropriate signature to initialise a Comparison delegate, i.e. it has two parameters of type string and a return type of int, so the C# compiler accepts it and invokes the appropriate overload of the Sort method.

VB 8 has no equivalent to C# anonymous methods but both VB 9 and C# 3.0 introduced lambda expressions as a supporting feature for LINQ. A lambda expression can be used to initialise a delegate in essentially the same way as an anonymous method:

C#

var strings = new List<string> {"The longest of all",
                                "Short",
                                "Even longer",
                                "Longer"};
 
Console.WriteLine("Before sorting:");
 
foreach (var str in strings)
{
    Console.WriteLine(str);
}
 
strings.Sort((x, y) => x.Length.CompareTo(y.Length));
 
Console.WriteLine("After sorting:");
 
foreach (var str in strings)
{
    Console.WriteLine(str);
}

VB

Dim strings As New List(Of String)
 
strings.Add("The longest of all")
strings.Add("Short")
strings.Add("Even longer")
strings.Add("Longer")
 
Console.WriteLine("Before sorting:")
 
For Each s In strings
    Console.WriteLine(s)
Next
 
strings.Sort(Function(x, y) x.Length.CompareTo(y.Length))
 
Console.WriteLine("After sorting:")
 
For Each s In strings
    Console.WriteLine(s)
Next

In each case the compiler infers the types of the lambda parameters based on the generic type of the list being sorted.

Just note that anonymous methods and lambdas make your code simpler if they themselves are simple but your code can quickly become difficult to read if you try to perform complex comparisons using an inline method.

So, let’s sum up the different ways we’ve discussed for sorting arrays and collections in the three parts of this series. First, we looked at performing automatic sorts on lists of objects that could compare themselves, thanks to their implementing the IComparable interface. Next, we looked at using the IComparer interface to create a class that could perform complex comparisons on objects that might not be inherently comparable. That class could also be reused in multiple code files or even multiple projects. Finally, we looked at using the generic Comparison delegate to invoke standard methods, anonymous methods and lambda expressions for simple custom sorts.

Happy sorting!

Wednesday, July 1, 2009

Sorting Arrays and Collections (Part 2)

Part 1 here

In the previous instalment we discussed how to use the IComparable interface to facilitate automatic sorting of arrays and collections. This time we will look at the IComparer interface and how it can be used to sort objects that may or may not be inherently comparable.

Like IComparable,the IComparer interface comes in both standard and generic flavours. Normally you would implement the generic version, although there may be instances where you need to implement the standard version. Sorting the items in a ListView is one such instance.

Again like IComparable, the IComparer interface declares only a single method. Where the IComparable.CompareTo method takes a single object and compares it to the current instance, the IComparer.Compare method takes two objects and compares them to each other. As an example, let's consider the case where you have a collection of strings that you want to sort by length instead of alphabetically. In this case we cannot rely on the IComparable implementation of the String class itself so we must define our own comparison, which we can do by implementing the IComparer interface:

C#

public class StringLengthComparer : IComparer<string>
{
    public int Compare(string x, string y)
     {
         return x.Length.CompareTo(y.Length);
     }
}

VB

Public Class StringLengthComparer
    Implements IComparer(Of String)
 
    Public Function Compare(ByVal x As String, _
                            ByVal y As String) As Integer _
    Implements IComparer(Of String).Compare
        Return x.Length.CompareTo(y.Length)
    End Function
 
End Class

Our StringLengthComparer.Compare method will take two Strings and return a result that indicates their relative order based on their Lengths. Notice that we are still making use of the IComparable.CompareTo method, which we know is a good convention to follow. In this case we are comparing the two Length properties, which are type Int32. The Int32 structure implements the IComparable interface so we should make use of it.

We can now perform a custom sort of a list of strings like so:

C#

List<string> strings = new List<string>();
 
strings.Add("The longest of all");
strings.Add("Short");
strings.Add("Even longer");
strings.Add("Longer");
 
Console.WriteLine("Before sorting:");
 
foreach (string str in strings)
{
     Console.WriteLine(str);
}
 
strings.Sort(new StringLengthComparer());
 
Console.WriteLine("After sorting:");
 
foreach (string str in strings)
{
     Console.WriteLine(str);
}

VB

Dim strings As New List(Of String)
 
strings.Add("The longest of all")
strings.Add("Short")
strings.Add("Even longer")
strings.Add("Longer")
 
Console.WriteLine("Before sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next
 
strings.Sort(New StringLengthComparer)
 
Console.WriteLine("After sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next

In this case we pass an instance of our StringLengthComparer class as an argument when we call Sort and all comparisons will be done using the Compare method of our class instead of the CompareTo methods of the objects in the list. Sorting arrays is much the same except, again, the Array.Sort method is static/Shared:

C#

string[] strings = {"The longest of all",
                    "Short",
                    "Even longer",
                    "Longer"};
 
Console.WriteLine("Before sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}
 
Array.Sort(strings, new StringLengthComparer());
 
Console.WriteLine("After sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}

VB

Dim strings As String() = {"The longest of all", _
                           "Short", _
                           "Even longer", _
                           "Longer"}
 
Console.WriteLine("Before sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next
 
Array.Sort(strings, New StringLengthComparer)
 
Console.WriteLine("After sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next

Now, our StringLengthComparer class is relatively simple. It only knows how to compare strings in one way. What if we want to be able compare objects in various ways depending on the circumstances? We can certainly do that with a class that implements IComparer. There’s no limit to the complexity of the class, as long as it provides the functionality defined by the interface. For example, let’s consider our Person class from the previous instalment:

C#

public class Person : IComparable, IComparable<Person>
{
     private string _lastName;
     private string _firstName;
 
     public string LastName
     {
        get { return this._lastName; }
         set { this._lastName = value; }
     }
 
     public string FirstName
     {
         get { return this._firstName; }
         set { this._firstName = value; }
     }
 
     public Person(string firstName, string lastName)
     {
         this._firstName = firstName;
         this._lastName = lastName;
     }
 
     public int CompareTo(object obj)
     {
         return this.CompareTo((Person) obj);
     }
 
     public int CompareTo(Person other)
     {
        // Compare by LastName by default.
        int result = this.LastName.CompareTo(other.LastName);
 
         // If LastNames are the same...
         if (result == 0)
        {
            // ...compare by FirstName.
            result = this.FirstName.CompareTo(this.FirstName);
        }
 
        return result;
     }
}

VB

Public Class Person
    Implements IComparable, IComparable(Of Person)
 
    Private _lastName As String
    Private _firstName As String
 
    Public Property FirstName() As String
        Get
            Return Me._firstName
        End Get
        Set(ByVal value As String)
            Me._firstName = value
        End Set
    End Property
 
    Public Property LastName() As String
        Get
            Return Me._lastName
        End Get
        Set(ByVal value As String)
            Me._lastName = value
        End Set
    End Property
 
    Public Sub New(ByVal firstName As String, _
                   ByVal lastName As String)
        Me._firstName = firstName
        Me._lastName = lastName
    End Sub
 
    Public Function CompareTo(ByVal obj As Object) As Integer _
    Implements System.IComparable.CompareTo
        Return Me.CompareTo(DirectCast(obj, Person))
    End Function
 
    Public Function CompareTo(ByVal other As Person) As Integer _
    Implements System.IComparable(Of Person).CompareTo
        'Compare by LastName by default.
        Dim result As Integer = Me.LastName.CompareTo(other.LastName)
 
        'If LastNames are the same...
        If result = 0 Then
            '...compare by FirstName.
            result = Me.FirstName.CompareTo(other.FirstName)
        End If
 
        Return result
    End Function
 
End Class

What if we want to be able to sort a list of Person objects by either FirstName or LastName? The Person class already implements the IComparable interface, which implementation compares by LastName then FirstName. As such, we could just provide an implementation of IComparer that compares by FirstName then LastName. That way we could either accept the default comparison or use our IComparer implementation, depending on the circumstances. For consistency though, a better idea might be to provide an IComparer implementation that can compare in either way and then use that all the time:

C#

public class PersonComparer : IComparer<Person>
{
    public enum ComparisonProperty
    {
        FirstName,
        LastName
    }
 
    private readonly ComparisonProperty _comparisonProperty;
 
    public PersonComparer(ComparisonProperty comparisonProperty)
    {
        this._comparisonProperty = comparisonProperty;
    }
 
    public int Compare(Person x, Person y)
    {
        int result = 0;
 
        switch (this._comparisonProperty)
        {
            case ComparisonProperty.FirstName:
                result = this.CompareByFirstName(x, y);
                break;
            case ComparisonProperty.LastName:
                result = this.CompareByLastName(x, y);
                break;
        }
 
        return result;
    }
 
    private int CompareByFirstName(Person x, Person y)
    {
        // Compare by FirstName by default.
        int result = x.FirstName.CompareTo(y.FirstName);
 
        // If FirstNames are the same...
        if (result == 0)
        {
            // ...compare by LastName.
            result = x.LastName.CompareTo(y.LastName);
        }
 
        return result;
    }
 
    private int CompareByLastName(Person x, Person y)
    {
        // Compare by LastName by default.
        int result = x.LastName.CompareTo(y.LastName);
 
        // If LastNames are the same...
        if (result == 0)
        {
            // ...compare by FirstName.
            result = x.FirstName.CompareTo(y.FirstName);
        }
 
        return result;
    }
}

VB

Public Class PersonComparer
    Implements IComparer(Of Person)
 
    Public Enum ComparisonProperty
        FirstName
        LastName
    End Enum
 
    Private ReadOnly _comparisonProperty As ComparisonProperty
 
    Public Sub New(ByVal comparisonProperty As ComparisonProperty)
        Me._comparisonProperty = comparisonProperty
    End Sub
 
    Public Function Compare(ByVal x As Person, _
                            ByVal y As Person) As Integer _
    Implements IComparer(Of Person).Compare
        Dim result As Integer
 
        Select Case Me._comparisonProperty
            Case ComparisonProperty.FirstName
                result = Me.CompareByFirstName(x, y)
            Case ComparisonProperty.LastName
                result = Me.CompareByLastName(x, y)
        End Select
 
        Return result
    End Function
 
    Private Function CompareByFirstName(ByVal x As Person, _
                                        ByVal y As Person) As Integer
        'Compare by FirstName by default.
        Dim result As Integer = x.FirstName.CompareTo(y.FirstName)
 
        'If FirstNames are the same...
        If result = 0 Then
            '...compare by LastName.
            result = x.LastName.CompareTo(y.LastName)
        End If
 
        Return result
    End Function
 
    Private Function CompareByLastName(ByVal x As Person, _
                                       ByVal y As Person) As Integer
        'Compare by LastName by default.
        Dim result As Integer = x.LastName.CompareTo(y.LastName)
 
        'If LastNames are the same...
        If result = 0 Then
            '...compare by FirstName.
            result = x.FirstName.CompareTo(y.FirstName)
        End If
 
        Return result
    End Function
 
End Class

Note that the CompareByLastName method compares in exactly the same way as the Person.CompareTo method. As such, we could have made use of that existing functionality in our PersonComparer class:

C#

private int CompareByLastName(Person x, Person y)
{
    return x.CompareTo(y);
}

VB

Private Function CompareByLastName(ByVal x As Person, _
                                   ByVal y As Person) As Integer
    Return x.CompareTo(y)
End Function

This does save us duplicating some code but it also makes the implementation of the PersonComparer class more reliant on the implementation of the Person class. By implementing our CompareByLastName method exactly as we want it, we allow the IComparable implementation of the Person class to change without changing the behaviour of our PersonComparer class.

We can now put our PersonComparer class to work in sorting a list of Person objects by either LastName or by FirstName:

C#

List<Person> people = new List<Person>();
 
people.Add(new Person("Mary", "Smith"));
people.Add(new Person("John", "Williams"));
people.Add(new Person("John", "Smith"));
people.Add(new Person("Andrew", "Baxter"));
 
Console.WriteLine("Before sorting:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new PersonComparer(PersonComparer.ComparisonProperty.LastName));
 
Console.WriteLine("After sorting by LastName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new PersonComparer(PersonComparer.ComparisonProperty.FirstName));
 
Console.WriteLine("After sorting by FirstName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0} {1}",
                                    person.FirstName,
                                    person.LastName));
}

VB

Dim people As New List(Of Person)
 
people.Add(New Person("Mary", "Smith"))
people.Add(New Person("John", "Williams"))
people.Add(New Person("John", "Smith"))
people.Add(New Person("Andrew", "Baxter"))
 
Console.WriteLine("Before sorting:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New PersonComparer(PersonComparer.ComparisonProperty.LastName))
 
Console.WriteLine("After sorting by LastName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New PersonComparer(PersonComparer.ComparisonProperty.FirstName))
 
Console.WriteLine("After sorting by FirstName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0} {1}", _
                                    person.FirstName, _
                                    person.LastName))
Next

As I said earlier, you can make your class as complex as you like, comparing objects in numerous different ways involving as many properties as you want.

Now, implementing the IComparer interface in a new class is a good option if you want to be able to compare instances of a type in multiple different ways and/or in multiple different places. If you only need to sort in one place, or at least only in one code file, then there is a slightly easier way. We’ll look at that in the next instalment.

Part 3 here