Friday, April 18, 2014

C#: Extension Methods (studying for Exam 70-483; Programming in C#)

Overview

C# extension methods were released as part of C# 3 in 2007 (Visual Studio 2008). In order to understand C# extension methods, consider .NET String class:

[SerializableAttribute]
[ComVisibleAttribute(true)]
public sealed class String : IComparable, 
ICloneable, IConvertible, IComparable<string>, 
        IEnumerable<char>, IEnumerable, IEquatable<string>

The sealed keyword means (to quote MSDN), "When applied to a class, the sealed modifier prevents other classes from inheriting from it." With respect to the String class there is no way to extend it through inheritance because it is sealed. This is where extension methods come in.

Using extension methods in an application can add methods to existing types such as the String class. These methods can be added without creating a new derived type (since this obviously not permitted due to the sealed keyword) or even recompiling the assembly for the original type. The String class or any type to which extension methods are applied remains unmodified.

When invoked by C# or VB.NET, extension methods  appear to be part of the type they are extending. For example it would be possible to extend the string class with methods ToGerman, ToFrench and ToItalian. The ToGerman method could be invoked as follows in the code:

string whatJFKDidNotSay = "I am a jelly doughnut.";
string aufDeutsch = whatJFKDidNotSay.ToGerman();

Even those with a rudimentary understanding of German recognize the translation to German is "Ich bin ein Pfannkuchen."

Extension methods are implemented using static method that is ultimately invoked as if the extension method were an instance method. An example of an extension method is as follows:

public static class StringGermanTranslation
{
  // The worst English to German translator every coded
  public static string ToGerman(this String text)
  {
    if (text == "I am a jelly doughnut.")
    {
      return "Ich bin ein Pfannkuchen.";
    }

    else
    {
      throw new Exception(
          "Unknown phrase cannot be translated: " + text);
    }
  }
}

The signature of the extension method is significant. The method is decorated with the static keyword and the first parameter is prefixed by the this keyword. Following "this" the type to be extended is specified (a.k.a. String). It is possible pass parameters to extension methods provided the type extended is the first parameter. To demonstrate this consider:

public static string ToGerman(this String text, 
                              bool fromNorthernGermany)
{
  if (text == "Saturday")
  {
    if (fromNorthernGermany)
    {
      return "Sonnabend.";
    }

    else
    {
      return "Samstag.";
    }
  }

  else
  {
    throw new Exception(
        "Unknown phrase cannot be translated: " + text);
  }
}

The second parameter of ToGerman allows a region to be specified so return a translation in either Northern German or Southern German. In reality the name used for Saturday in German is more complex than Northern versus Southern Germany. 

The overloaded method of ToGerman including a bool to specify region is invoked as follows:

string day = "Saturday";
// true since we are from northern Germany
string tagAufDeutsch = day.ToGerman(true);

LINQ 

Many developers have noticed that their code generate a significant number of compilation errors if the remove the following from a C# source file:
using System.Linq;

LINQ added query functionality to the types:
  • System.Collections.IEnumerable
  • System.Collections.Generic.IEnumerable<T>

The mechanism to extend the aforementioned types is extension methods. When "using System.Linq;" is omitted, the LINQ provided extension methods are not recognized and generate a variety of compiler errors.

Extending Interfaces

If LINQ can extend a .NET interface, any C# or VB.NET developer can extend an interface. Consider the following class which extends .NET IDisposable interface:

public static class ExtendIDisposable
{
  public static void DisposeAndDisplayMessage(
             this IDisposable idisposable, string message)
  {
    Console.WriteLine(message);
    idisposable.Dispose();
  }
}

The following code can be written making use of the DisposeAndDisplayMessage extension method: 

using (var file = File.CreateText(Path.GetTempFileName()))
{
  file.WriteLine("Hi mom, I miss you.");
  file.DisposeAndDisplayMessage(
    "Guess it's time to close the file. Bye mom.");
}

The method File.CreateText returns type System.IO.StreamWriter. StreamWriter implements IDisposable so our extension method changed the behavior of a StreamWriter class via IDisposable.

Null Instances

Consider the following code where the value of the file variable was assigned to null below:
using (var file = File.CreateText(Path.GetTempFileName()))
{
  file.WriteLine("Hi mom, I miss you.");
  file = null;
  file.DisposeAndDisplayMessage(
    "Guess it's time to close the file. Bye mom.");
}

The question is, "Will DisposeAndDisplayMessage be invoked?" The answer is yes. The DisposeAndDisplayMessage  is invoked. Recall this method is implemented as follows:

  public static void DisposeAndDisplayMessage(
             this IDisposable idisposable, string message)
  {
    Console.WriteLine(message);
    idisposable.Dispose();
  }

The line of code "idisposable.Dispose();" will generate a null access exception. When implementing extension methods develops need to recognize the first parameter corresponding to the type extended can be null.

No comments :

Post a Comment