AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1583&pId=-1
Useful Extension Methods
page
by Brian Mains
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 24014/ 59

Introduction

Extension methods are very useful.  They help extend the existing objects of .NET by attaching methods to it that it appears that it's a part of the object, but it's really not.  Let's look at some extension methods and how they can be useful.

The Anatomy of an Extension Method

The following is an extension method

Listing 1

namespace Nucleo.Primitives
{
  public static class StringExtensions
  {
    public static bool IsMatch(this string value, string regularExpression)
    {
      return Regex.IsMatch(value, regularExpression);
    }
  }
}

Let's look at it in detail.  First, note the usage of the "this" keyword; whatever type that is designated for the property with the "this" keyword is the type that will be extended.  So, in this example, this method is added to the string class.  This means that you could do:

Listing 2

string value = "4545";
Assert.IsTrue(value.IsMatch(@"\d+"));

And this will work.  When you call IsMatch, it invokes our static StringExtensions IsMatch method.  This is a requirement; both the class and method have to be defined as static.  Secondly, the first parameter must be defined with the "this" keyword.  Note one thing; the first parameter defines the type to extend, but the rest of the parameters are the parameters of the instance method on the class.

What I mean by that is IsMatch has one string parameter: regularExpression.  If the method were defined as

Listing 3

public static bool IsMatch(this string value, string regularExpression, 
RegexOptions options) 
{ 
}

You would invoke it as

Listing 4

Assert.IsTrue(value.IsMatch(@"\d+", RegexOptions.None));

So, for every parameter after the "this" parameter, these are the parameters of the method.  Also note that you can use whatever return type you want (void, bool, string) as it's the return type of the method, and you can also define whatever scope you want, though the most beneficial is public scope.

One thing to note: our static extension is defined in the Nucleo.Primitives namespace; this means that you have to define the following using statement for the extension method to even appear

Listing 5

Using Nucleo.Primitives;

Otherwise, the extension method will not appear for any string instances.

Extension Method Application

As you can see, extension methods are useful.  One thing I commonly do is add extension methods for business objects, to provide shortcuts when accessing objects.  For instance, suppose you had a Student class.  This student registers for Courses, which are linked to a Semester.  All of the capitalized objects mentioned in the past two sentences are the tables, creating a relationship from Student to Courses and from Semester to Courses, with Courses being the many-to-many table.

Suppose the application wants to get only the courses for the current semester, but doesn't delete the past semesters from the database, for historical tracking purposes.  To keep the previous semesters out of the query results, the Semester table has an IsCurrent flag, denoting the current semester with a bit value of 1 in a SQL Server database.

This query is in a database and in the BAL/DAL, but I often write an extension method as such

Listing 6

public static class StudentExtensions
{
  public static IEnumerable<Course> GetCurrentCourses(this Student student)
    {
      CoursesService service = new CoursesService();
      return service.GetCurrentCourses(student.Key);
    }
}

The CoursesService is a business object that gets the data from the data access layer and converts it to course business objects.  You may ask why not embed this directly in the student object?  Well, your business layer may be auto-generated by an ORM mapping software, like IdeaBlade, Wilson ORMapper, LINQ-to-SQL classes, etc., or it may be an assembly where the Student class is not inheritable and not defined as a partial class.  ORM mappers often regenerate the classes when changes are made or if you have to clear the entire setup and redo all your mappings.  Extension methods extract the logic and save your code from this happening.  Extension methods are best for classes marked as sealed/not-inheritable because you can append your methods to these objects through extension methods.

Extension methods help to shortcut code and encapsulate references.  What I mean by that is in the simple example above, it hid the fact that it used a service class to get the courses, and the fact that courses are retrieved by the student's key.  How about getting all the semesters a student has been involved in, where a student's course grades are worthy of honors?

Listing 7

Public static IEnumerable < Semester > GetCoursesWithHonors(this Student
  student)
{
  CoursesService service = new CoursesService();
  IEnumerable < Course > courses = service.GetAllCourses();
  return (from c in courses Where c.Grade > 3.4 && c.IsCompleted == true select
    c.Semester).Distinct();
}

This way, you can simply invoke the following statement, when binding to an ASP.NET GridView

Listing 8

this.StudentGridView.DataSource = student.GetCoursesWithHonors();
this.StudentGridView.DataBind()

All of the work for processing the record and for determining what grades are included in honors, are encapsulated into one single method for you.  It also works when updating or creating new records.  For instance, suppose you get the student's record, but if the student doesn't exist, a record is created for them.  This would be based on the key value, which in this case is first and last name (in most cases, there is a student ID).  This could be done with the following

Listing 9

public static Student CreateOrGetStudent(string firstName, string lastName)
{
  StudentService service = new StudentService();
  Student student = service.GetByName(firstName, lastName);
  if (student == null)
  {
    student = new Student(firstName, lastName);
    service.Add(student);
  }
  return student;
}

There are other useful things you can do with extension methods, like formatting text

Listing 10

public static string TrimText(this string text, bool ensurePeriodAtEnd)
{
  if (text == null)
    return string.Empty;
 
  text = text.Trim();
  if (string.IsNullOrEmpty(text))
    return string.Empty;
  if (text == "\"\"")
    return string.Empty;
 
  if (string.StartsWith("\""))
    text = text.Substring(1);
  else if (string.EndsWith("\""))
    text = text.Substring(0, text.Length - 1);
  text = text.Trim();
 
  if (ensurePeriodAtEnd && !text.EndsWith("."))
    text += ".";
 
  return text;
}

I often work with data like Excel data, where if the comma exists inside the data, it's wrapped with double-quotes.  This method helps strip off those outside double-quotes, as well as ensure it's a proper sentence with a period at the end.  It also ensures that if null or empty, it returns an empty string safely.

How about the list control in ASP.NET?  Extension methods can work with any object, which also counts for ASP.NET or windows server controls.  How about the need to select all the items in the list (I saw a ClearSelection() method, but did not see a select all method)?  The following static extension method attaches to any class that derives from ListControl, and allows you to invoke the SelectAll method, selecting all items in the list.

Listing 11

public static void SelectAll(this ListControl control)
{
  foreach (ListItem item in control.Items)
  item.Selected = true;
}

How about the use of reflection?  Although easy to use, it's helpful to have a method that shortcuts the accessing of properties, making property statement determinations (such as whether the property can read or write), and casting the value.  The following extension methods may be helpful in extracting values from an underlying business object and returning the property name.  Note that it extends the object type, which means it can work for any object in the .NET Framework.

Listing 12

public static object GetPropertyValue(this object value, string propertyName)
{
  return GetPropertyValue(value, propertyName, null);
}
public static object GetPropertyValue(this object value, string propertyName,
  object[]indexes)
{
  if (value == null)
    throw new ArgumentNullException("value");
  if (string.IsNullOrEmpty(propertyName))
    throw new ArgumentNullException("propertyName");
 
  PropertyInfo property = value.GetType().GetProperty(propertyName,
    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  if (property == null)
    throw new Exception("The property does not exist for the target object.");
  if (!property.CanRead)
    throw new Exception("The property cannot be read.");
 
  return property.GetValue(value, indexes);
}
public static T GetPropertyValue < T > (this object value, string propertyName,
  object[]indexes)
{
  return (T)GetPropertyValue(value, propertyName, indexes);
}

The last method defined is a convenience method; it defines a generic type that can be used to strongly-type the return value.  However, ensure this matches the return value, because you may get an InvalidCastException result from it.

Conclusion

Extension methods are very powerful; they can help you move code outside of automatically generated code, so you don't have to do any extra maintenance work there.  They also can attach methods to objects that are sealed or not-inheritable in VB.NET. But one of the more important facts is that they can reduce the amount of code and create reusable logic that is simpler to execute.

Extension methods, used wisely, can be a very powerful thing, and can add a huge convenience for an application.  It also encapsulates code and can assist with code reuse by placing it in the static class where it can be reused for every instance of the extended type.



©Copyright 1998-2014 ASPAlliance.com  |  Page Processed at 10/30/2014 8:13:10 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search