Wednesday, November 5, 2008

C# Interfaces v/s Concrete Classes

When designing architectures in .NET, we frequently use interfaces for parameter types in our method signatures. This post will help to explain why we should choose to do this and the benefits of coding in this manner.

Let's just say that you had the following two methods implemented in your data-access layer. The first calls the database and returns a result set to the SqlDataReader. The second method fills the a list of articles by iterating through the result set in the SqlDataReader and adding an article to the list for each row in the result set. Let's assume it looked like this:

public IList
Get()
{
SqlConnection connection = new SqlConnection(_connectionString);
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "GetAllArticles";
SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleResult);
return FillArticles(reader);
}

private IList
FillArticles(SqlDataReader reader)
{
List
articles = new List
();
while (reader.Read())
{
Article article = new Article();
article.ArticleID = (int)reader["ArticleID"];
article.Title = reader["Title"];
article.Body = reader["Body"];
article.Published = (DateTime)reader["Published"];
articles.Add(article);
}
return articles;
}

As you can see, the FillArticles method is expecting a SqlDataReader (a concrete class). Now let's assume that you are told that articles will no longer be stored in the database, but rather in XML files. In order for you to make this change, you will need to refactor the Get() method to handle XML access and then pass an XmlReader to the FillArticles() method. Unfortunately, you will get an error because it is expecting a SqlDataReader.

How do we fix this? Well, in short, both SqlDataReader and XmlReader implement an interface called IDataReader which requires these methods to be defined: Read, NextResult, Close, RecordsAffected, etc. By changing the parameter type from SqlDataReader to IDataReader, you can still use the Read() method; however, you can now pass in any concrete class that implements IDataReader. Here is what the refactored code will look like:

private IList
FillArticles(IDataReader reader)
{
List
articles = new List
();
while (reader.Read())
{
Article article = new Article();
article.ArticleID = (int)reader["ArticleID"];
article.Title = reader["Title"];
article.Body = reader["Body"];
article.Published = (DateTime)reader["Published"];
articles.Add(article);
}
return articles;
}

There are many ways to determine which common base types are available to use as parameters. One feature you can use is the object browser provided by .NET (Alt + Ctrl + J); search for the concrete class and expand the base types folder to see which are implemented. Additionally, ReSharper will tell you if you can refactor the parameter type based on which methods you use. Finally, Lutz Roeder's .NET Reflector will allow you to find the base types for each class.

Thinking about future issues and maintenance problems while developing projects will save lots of heartache when design changes are made late in the process. I hope this made sense. If I can clarify anything, please let me know.

Sources : http://lowrymedia.com/blogs/technical/

http://www.skyscrapr.com/

1 comment:

Anonymous said...

Nice explanation dude. Simply and clearly explained the use of Interface. Thanks.