Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / C#

A short tale on two patterns that live in .NET

Rate me:
Please Sign up or sign in to vote.
4.74/5 (9 votes)
2 Dec 2017CPOL6 min read 14.4K   5   6
Exploring some patterns that are used in the .NET framework and how they could be usefull

Introduction

A lot of developers on the forums are sceptical about design patterns, wondering if the silver bullet actually adds any real value or just adds clutter and complexity. My last employer was using patterns rather succesfully and I'd like to share some of the things I learned by looking at some common patterns that are visible in the source-code and implementation of the .NET framework.

The factory

To start with something that at least one member hates, a factory is basically a thing (class or method) that returns a new object. Don't worry about all the formal different factories out there; we're going to ignore them completely. We're also leaving out any classification on the type of pattern, as it matters little to the computer whether it is a creational or behavioural pattern.

An underused factory in the .NET framework is the DBProviderFactory[^];

C#
using System.Data.Common;

static DbProviderFactory dbProviderFactory = 
  DbProviderFactories.GetFactory("System.Data.SqlClient");

Yes, a factory that creates a SqlConnection. Vary the parameter, and you get a different connection. If every database-call uses this factory to create connections, then changing the type of the connection becomes simpeler. Instead of replacing all those "new SqlConnection()" statements with "new SQLiteConnection()" statements, you only need to change the argument to the factory.

"But Eddy", you'll say, "we also need to rewrite all those SqlCommand calls". Yes, most people will use the code-pattern below to do database- related work;

C#
using (var con = new SqlConnection())
using (var cmd = new SqlCommand())
{
    cmd.Connection = con;
    // more here

You can see how both classes are hardcoded, so replacing the connection-creation with a factory only helps us halfway. Do we pass the DbProviderFactory as an extra parameter? We could, and it implements a CreateCommand and CreateParameter method. Luckily, we don't need to, as the IDbConnection[^]-interface demands that all connections implement a CreateCommand factory. Yes, you read that right, it is not SOLID[^] to include a factory-method in all your connections, but it does make life darned easy;

C#
using (var con = MyConnectionFactory.CreateConnection())
using (var cmd = con.CreateCommand())
{
    // no longer a need to assign the Connection-property of the command,
    // since the connection creating the command is assigned by default.
}

So now we are using a factory-class (DbProviderFactory) to create the connection, and a factory-method on that specific connection to create the command. There are factory-methods on the command to create Parameters, which will not surprise anyone at this point. Instead of littering our code with "new bla()", we do the creation in a single point where we can more easily control it; that has some more advantages than simply not having to update all those points in code when replacing the object created. It means you can do some initialization of the object or keep track of it easily. As an example I've included below the factory that I used in this example. I moved the opening of the connection to the factory, meaning that this statement no longer has to be repeated in every place where a connection is fetched.

C#
public static class MyFactory
{
    static DbProviderFactory dbProviderFactory =
             DbProviderFactories.GetFactory("System.Data.SqlClient");

    public static IDbConnection CreateConnection()
    {
        var con = dbProviderFactory.CreateConnection();
        con.Open();
        return con;
    }
}

If your boss asks to log each opening of the database, then it would be easy to add that to the code above. Since the CreateCommand method on the connection also sets the Connection-property of the command, you now can omit two common statements in this pattern: opening the connection, and assigning the connection to the command. That's two less lines per database-operation that could fail.

Verdict

So the factories are used in the .NET framework and their advantages are easy to explain. Combine this technique with adhering to the SQL92 standard and you are suddenly capable of utilizing a whole range of database products as your storage. Using a different database from another vendor may suddenly be just an issue of configuration, instead of months of migrating.

Religious warning

I am not saying that you should only create factories and never use the new-keyword again. In the example there is obvious value in the extra code that is required, but don't use a factory just because you can; if you can do without, then that is probably the better idea. Do not add complexity if it does not add value, as all code comes with a cost. It is also usless to learn the definition of an abstract factory; learn when (and when not) to apply the pattern, instead of correctly naming it.

The decorator

A decorator is a nice and easy way of adding stuff to a class in a more flexible way than inheritance. A simple abstract example follows below;

C#
class Person
{
    public string Name { get; set; }
    public string SocialSecurityId { get; set; }

    public void ShowId();
}
class ClothedPerson: Person
{
    Person _decoree;
    ClothedPerson(Person who, bool hasPants)
    {
        _decoree = who;
    }
    public bool Pants { get; set; }
    public void ShowId() { _decoree.ShowId(); }
}
class Prisoner: Person
{
    Person _decoree;
    Prisoner(Person who)
    {
        _decoree = who;
    }
    public void ShowId() { _decoree.ShowId(); }
}

That's a useless example and I already hear you say that you'd never use that in real code, but if you take a look, this is how streams work in .NET; Need encryption on your (file/memory/whatever)stream? Add the correct decorator, and you're done. Now, lets take a look at a more practical example of a decorator that may be more usefull.

C#
  1  public class LoggedConnection : IDbConnection
  2  {
  3      ILogger _logger;
  4      IDbConnection _host;
  5      Guid _connectionId;
  6      string _category;
  7  
  8      public Guid ConnectionId
  9      {
 10          get { return _connectionId; }
 11      }
 12  
 13      public LoggedConnection(IDbConnection host, ILogger logger)
 14      {
 15          if (null == host) throw new ArgumentNullException("host");
 16          if (null == logger) throw new ArgumentNullException("logger");
 17  
 18          _host = host;
 19          _connectionId = Guid.NewGuid();
 20          _category = string.Format("{0} ({1})", _host.GetType().Name, _connectionId);
 21          _logger = logger;
 22  
 23          _logger.Write(
 24              _category,
 25              "Created a new IDbConnection to database '{0}'",
 26              _host.Database);
 27      }
 28  
 29      public string ConnectionString
 30      {
 31          get { return _host.ConnectionString; }
 32          set { _host.ConnectionString = value; }
 33      }
 34  
 35      public int ConnectionTimeout
 36      {
 37          get { return _host.ConnectionTimeout; }
 38      }
 39  
 40      public string Database
 41      {
 42          get { return _host.Database; }
 43      }
 44  
 45      public ConnectionState State
 46      {
 47          get { return _host.State; }
 48      }
 49  
 50      public IDbTransaction BeginTransaction()
 51      {
 52          return _host.BeginTransaction();
 53      }
 54  
 55      public IDbTransaction BeginTransaction(IsolationLevel level)
 56      {
 57          return _host.BeginTransaction(level);
 58      }
 59  
 60      public void ChangeDatabase(string dbName)
 61      {
 62          _host.ChangeDatabase(dbName);
 63      }
 64  
 65      [DebuggerStepThrough()] // this makes sure that any error
 66                              // points to the Open() method of the _host.
 67      public void Open()
 68      {
 69          _logger.Write(
 70              _category,
 71              "Opening database '{0}' with ConnectionString '{1}'",
 72              _host.Database,
 73              _host.ConnectionString);
 74  
 75          _host.Open();
 76      }
 77  
 78      public void Close()
 79      {
 80          _logger.Write(
 81              _category,
 82              "Closing database '{0}'",
 83              _host.Database);
 84  
 85          _host.Close();
 86      }
 87  
 88      public IDbCommand CreateCommand()
 89      {
 90          return new LoggedCommand(_host.CreateCommand(), _connectionId, _logger);
 91      }
 92  
 93      void IDisposable.Dispose()
 94      {
 95          Close();
 96          _host.Dispose();
 97          System.GC.SuppressFinalize(this);
 98  
 99          _logger.Write(
100              _category,
101              "Disposed IDbConnection to database '{0}'",
102              _host.Database);
103      }
104  }

You're looking at a decorator for the IDbConnection interface; only sharing a part here to show how the concept works. To get it working, you'd also need a decorator for the IDbCommand and the IDataReader interface which I can't share since they're part of a deliverable. As you can see, we added a logger to the connection; you can see how the factory-method called "CreateCommand" is implemented to return a decorated version of the underlaying command. If you implement the decorator for the IDbCommand interface, then you can execute a decorated IDataReader from there. The concept is simple, yet powerfull. Combine both the factory and decorator patterns, and you'll be able to trace ("profile") all the SQL statements that are executed over the decorated provider (regardless whether it is SQL Server, SQLite, or MS Access).

The connectionId is there to uniquely identify each connection, and as you can see, I forgot to mention the id when the connection is closed. It proves that knowing patterns doesn't make you immune to stupidity.

It is used in a fashion similar to the code shown below;

C#
ThreadPool.QueueUserWorkItem((w) =>
{
    using (var loggedTestDatabase = FetchConnection(true))
    {

With the local factory shown below;

C#
static IDbConnection FetchConnection(bool trace)
{
    if (null == factory)
        factory = DbProviderFactories.GetFactory(providerInvariantName: providerString);

    IDbConnection connection = factory.CreateConnection();

    if (trace)
    {
        // see ILogger CoClass attribute on what class is created.
        ILogger logger = new ILogger(includeThreadName: true, includeDateStamp: true);
        connection = new LoggedConnection(connection, logger);
    }

    return connection;
}

This means we can now "profile" any legal Data Provider, and can switch this behaviour on and off simply. If a new Data Provider comes along, there's no need to update or rewrite anything, it'll just work. That's just with the help of two simple patterns that most of us will easily grasp. This means having logging without having a grunt to write all the log-statements.

You see that the logger is not coming from a factory; I'm not in the habit of replacing all constructors with factories - if a factory isn't required, then it is preferred to not have it. In the above example, both patterns usage is justified by the added functionality. Developers often argue about what is "right"; them arguing is being right, as it means that someone is justifying his/her decisions. Those decisions may be right or wrong, but arguing is the only way to find out.

Anti-patterns

Here's an anti-pattern for you, an improvement in the eyes of StyleCop, and a rule I break often; SA1122 says to use a nullobject-pattern, String.Empty instead of hardcoding "". It does not provide any benefits, it just uses more characters to convey the same information, making it harder to read than the original. I do like EventArgs.Empty, but have accepted long time ago that the EventArgs member will usually be null in custom brownfields. How about you? Do you like string.Empty?

What else is there?

There's many more patterns; they won't solve your problems automatically as is being advertised here and there, but they will give you some extra ammo in approaching existing stuff. Is there a pattern that you want me to take a look at? Leave a comment!

History

  • December 2, 2017 - Initial Version

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Currently none.
Netherlands Netherlands
I'm a Delphi-convert, mostly into WinForms and C#. My first article is from 2001, extending the Delphi-debugger, which is still visible on the WayBackMachine[^] and even available in Russian[^] Smile | :)

Comments and Discussions

 
GeneralMy vote of 5 Pin
BillWoodruff5-Feb-18 19:00
professionalBillWoodruff5-Feb-18 19:00 
GeneralRe: My vote of 5 Pin
Eddy Vluggen6-Feb-18 6:39
professionalEddy Vluggen6-Feb-18 6:39 
QuestionString.Empty Pin
dmjm-h4-Dec-17 9:29
dmjm-h4-Dec-17 9:29 
AnswerRe: String.Empty Pin
Eddy Vluggen4-Dec-17 12:53
professionalEddy Vluggen4-Dec-17 12:53 
GeneralRe: String.Empty Pin
David A. Gray5-Dec-17 20:29
David A. Gray5-Dec-17 20:29 
GeneralRe: String.Empty Pin
Eddy Vluggen6-Dec-17 0:49
professionalEddy Vluggen6-Dec-17 0:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.