Skip navigation

Extension Methods

Add Your Own Functions to Existing .NET Classes

ControlFreak

LANGUAGES: VB.NET | C#

ASP.NET VERSIONS: 3.5

 

Extension Methods

Add Your Own Functions to Existing .NET Classes

 

By Steve C. Orr

 

These days, it s easy for a developer to feel spoiled by the seemingly infinite number of useful classes and methods included in the .NET Framework. A novice developer might even be forgiven for thinking they d never need to add their own functionality to such a rich toolset even though experienced developers know otherwise. However, even experienced developers might not agree on the best way to add such new features. For example, consider the function in Figure 1 that counts and returns the number of words contained in a string.

 

Public Function WordCount(ByVal s As String) As Integer

 Dim i As Integer

 Dim c = New Char() {" "c, "."c, "!"c, "?"c}

 i = s.Split(c,StringSplitOptions.RemoveEmptyEntries).Length

 Return i

End Function

Figure 1: This homeless VB.NET function returns the number of words in a given string.

 

The remaining question is: Where should such a function go? Some reasonable choices are a public VB module or a static C# class. It could also go in a class library project for improved reusability. Grouping it with a set of related functions is also a logical decision. One remaining concern would then be discoverability. Your fellow developers cannot use this function if they don t know it s there. The classic solution for discoverability documentation is nearly always a good idea, even though documents tend to be useful only when they are read by the right people at the right time. These days, the best programming documentation is the kind that s built-in to development tools and automatically offered up at key moments such as Visual Studio IntelliSense.

 

To coax Visual Studio into offering up this string-related function at the right moments, it might be best to add it to the .NET Framework s String class. While it may be possible to get the source code for the String class and add your own methods to it, such a relatively complex decision could have significant repercussions. A more object-oriented choice would be to inherit the .NET String class and extend it with custom functions. Unfortunately, many .NET classes are sealed, so this technique cannot be used consistently.

 

What Are Extension Methods?

Extension methods are effectively static functions that can be attached to any class. Thanks to the .NET 3.5 compilers and Visual Studio 2008, any class can be extended with new methods, even if the underlying class is sealed, pre-compiled, and thoroughly obfuscated. You can extend your own classes, .NET classes, or even third-party classes. There is no need for access to the source code of the class.

 

To attach to the .NET Framework s String class the WordCount function shown in Figure 1, it merely needs to be prefixed with the Extension attribute of the System.Runtime.CompilerServices namespace (see Figure 2).

 

<Extension()> _

Public Function WordCount(ByVal s As String) As Integer

 Dim i As Integer

 Dim c = New Char() {" "c, "."c, "!"c, "?"c}

 i = s.Split(c,StringSplitOptions.RemoveEmptyEntries).Length

 Return i

End Function

Figure 2: The Extension attribute added to the beginning of this VB.NET function assigns it to become a member of the String class.

 

Now the WordCount function will appear in Visual Studio 2008 IntelliSense when you type a period after any string variable (see Figure 3). Extension methods appear with a unique icon so you can tell them apart from regular methods, even though you ll rarely need to care.

 

Figure 3: An extension method as displayed by Visual Studio 2008 IntelliSense.

 

Mixing languages is no real problem, either. If desired, you can create a class library filled with extension methods in one language (such as C#), then add a reference to that assembly from any other .NET language.

 

What about C# Extension Methods?

Extension methods are supported by the C# compiler, as well. Instead of prefixing functions with the Extension attribute, the this keyword must be used with the parameter and the function must be explicitly declared as static. Figure 4 shows the C# translation of the WordCount function using this alternate syntax.

 

public static int WordCount(this string s)

{

 var i = 0;

 var c = new char[] { " "[0], "."[0], "!"[0], "?"[0] };

 i=s.Split(c,StringSplitOptions.RemoveEmptyEntries).Length;

 return i;

}

Figure 4: Instead of using the Extension attribute, C# developers use the this keyword with the parameter to specify extension methods.

 

Extension methods also can be a great way to bring language-specific features in to the more global .NET world. For example, when developing in C#, I often miss VB s IsNumeric function (as there is no direct equivalent in C#). By adding a reference to Microsoft.VisualBasic.dll, an IsNumeric extension method can be added to the String class:

 

public static bool IsNumeric(this string s)

{

 return Microsoft.VisualBasic.CompilerServices.IsNumeric(s);

}

 

Because of the improved discoverability, even VB developers can appreciate such an extension. Now all .NET developers can use the same code to determine if a string contains a numeric value, regardless of which language is being used:

 

return MyString.IsNumeric()

 

Further Improvements to the String Class

In the same vein, the usability of existing .NET Framework functions can be improved via extension methods. Consider the static IsNullOrEmpty method that was added to the String class in version 2.0 of the .NET Framework. This simple one-line extension method can improve the readability significantly:

 

public static bool IsNullOrEmpty(this string s)

{

 return System.String.IsNullOrEmpty(s);

}

 

So now, instead of having to use this syntax:

 

String.IsNullOrEmpty(MyString)

 

you can use this more intuitive syntax:

 

MyString.IsNullOrEmpty()

 

By now your mind may be wandering about other useful methods that could be added to the String class to give it enhanced functionality. For example, how about adding some basic encryption functionality to the String class? The following C# extension method hashes the attached string using the MD5 algorithm:

 

/// <summary>

/// Hashes the string with the MD5 algorithm

/// </summary>

/// <returns>A hashed version of the string</returns>

public static string MD5Hash(this string s)

{

 byte[] bDat = new UnicodeEncoding().GetBytes(s);

 byte[] bHash =

        new MD5CryptoServiceProvider().ComputeHash(bDat);

 return BitConverter.ToString(bHash);

}

 

Encapsulate Regular Expressions

There are a virtually infinite number of useful regular expressions to help developers match patterns and operate on strings. Memorizing them all is even more impractical than trying to remember the complex syntax required to construct your own. So once a developer has gone through all the work of finding (or piecing together) a needed regular expression, the result is usually a valuable piece of work that should not be lost. This is another great case for extension methods.

 

Consider a function that strips HTML tags from a string. Such a function is not complicated; in fact, it can be done with a single line of code:

 

Regex.Replace(s, @"<(.|n)*?>", "") // Removes HTML

 

But rather than trying to remember this arcane syntax (and potentially having to do a global search and replace if you ever decide to change it) you can centralize the function in an intuitive way by extending the String class:

 

/// <summary>

/// Removes any HTML markup from the string

/// </summary>

/// <returns>An HTML-free version of the string</returns>

public static string StripHTML(this string s)

{

 return Regex.Replace(s, @"<(.|n)*?>", "");

}

 

Other common regular expressions can be encapsulated too, so they need not be memorized or scattered carelessly about. This C# extension method determines whether the attached string object contains a syntactically correct e-mail address:

 

/// <summary>

/// Determines whether the string contains an email address

/// </summary>

/// <returns>True if the string contains an email</returns>

public static bool IsEmailAddress(this string s)

{

 var regex = new Regex(@"^[\w-\.][email protected]([\w-]+\.)+[\w-]{2,4}$");

 return regex.IsMatch(s);

}

 

Similarly, this next extension method determines whether or not the attached string object contains a syntactically correct Web address:

 

/// <summary>

/// Determines whether the string contains a URL

/// </summary>

/// <returns>True if the string contains a URL</returns>

public static bool IsUrl(this string s)

{

 var reg = @"http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?";

 Regex regex = new Regex(reg);

 return regex.IsMatch(s);

}

 

You could take this line of thought a step further by also determining whether a syntactically correct Web address is also a real and active Web page. The extension method in Figure 5 calls the previous IsUrl extension method to validate the string as syntactically correct, then attempts to ping the URL.

 

/// <summary>

/// Attempts to ping the string as a URL

/// </summary>

/// <returns>True if the string is a responsive URL</returns>

public static bool IsUrlResponsive(this string s)

{

 if (s.IsUrl())

 {

  var p = new System.Net.NetworkInformation.Ping();

  s = s.ToLower().Trim().Replace("http://", "");

  s = s.Replace("https://", "");

  var pr = p.Send(s);

  return (pr.Status ==

      System.Net.NetworkInformation.IPStatus.Success);

 }

 return false;

}

Figure 5: Call the IsUrl extension method to ensure the string is syntactically correct, then try to ping the URL.

 

Extend All Classes

While the String class is certainly ripe for extension methods, it is far from the only class that can benefit from them. The type of class being extended is specified by the parameter type passed to the extension method. For example, the following extension method determines whether the attached integer is an even number or not:

 

/// <summary>

/// Determines whether the number is even or odd

/// </summary>

/// <returns>True if the number is even</returns>

public static bool IsEven(this int i)

{

 return ((Math.IEEERemainder(i, 2) == 0));

}

 

And because it s so simple, you might as well throw in an IsOdd method, as well:

 

/// <summary>

/// Determines whether the number is even or odd

/// </summary>

/// <returns>True if the number is odd</returns>

public static bool IsOdd(this int i)

{

 return ((Math.IEEERemainder(i, 2) != 0));

}

 

You can even add extension methods to the base Object class so that every class in the .NET Framework will be extended. Consider this extension that adds an IsNull method to all objects:

 

/// <summary>

/// Determines whether an object has been initialized

/// </summary>

/// <returns>True if the object is initialized</returns>

public static bool IsNull(this object o)

{

 return (o == null);

}

 

Then you ll be able to write code like this:

 

If MyCol.IsNull() Then MyCol = New Collection()'VB.NET

 

You can override extension methods to create special versions for certain classes if needed. For example, the following IsNull extension method specialized for the DateTime class replaces (and does not conflict with) the generic version previously listed:

 

/// <summary>

/// Determines if a DateTime variable has been initialized

/// </summary>

/// <returns>True if initialized</returns>

public static bool IsNull(this DateTime dt)

{

 if (dt == null) return true;

 return (dt==DateTime.MinValue || dt==DateTime.MaxValue);

}

 

This IsNull method extends the DateTime object with special business logic that considers a date to be null if it contains either the minimum or maximum possible values.

 

Multiple Parameters

Extension methods can accept parameters just like any other method can. The first parameter must always specify the type of class being extended, but any number of standard parameters may follow. To illustrate this fact, consider the method in Figure 6 that extends all string arrays.

 

/// <summary>

/// Determines whether the array contains the string

/// </summary>

/// <param name="strings">string to search for</param>

/// <returns>true if array contains the string</returns>

public static bool IsInArray(this string[] strings,string s)

{

 foreach (string str in strings)

 {

   if (s == str) return true;

 }

 return false;

}

Figure 6: Extend all string arrays.

 

The first parameter in Figure 6 (decorated with the this keyword) specifies that all string arrays are being extended by this method. The second parameter accepts the string to seek within the array. The extension method returns true if the string value is found within the string array:

 

The method can be invoked with the following code:

string[] MyStrings = {"hello", "world"}; //Create string array

return MyStrings.IsInArrary("world"); //Returns true

 

Conclusion

Extension methods are a simple and reliable way to add methods to any existing class. I suspect a future version of the .NET Framework will add concepts such as extension properties and extension events. In fact, extension properties and events actually came first as part of Windows Presentation Foundation (WPF) and Silverlight. Curiously, extension methods were not included. It seems inevitable that all such extensions will be unified as a part of the base .NET Framework at some point in the future.

 

The source code accompanying this article is available for download.

 

Steve C. Orr is an ASP Insider, MCSD, Certified ScrumMaster, Microsoft MVP in ASP.NET, and author of Beginning ASP.NET 2.0 AJAX (Wrox). He s been developing software solutions for leading companies in the Seattle area for more than a decade. When he s not busy designing software systems or writing about them, he can often be found loitering at local user groups and habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net or e-mail him at mailto:[email protected].

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish