Larry Steinle

January 25, 2011

Extending IEnumerable

Filed under: VS.Net — Larry Steinle @ 11:57 pm
Tags: , , , , , , , ,

Often there is the need to take a string value and split it into an array of values. String.Split provides a quick and convenient method to split strings (as long as you don’t have to worry about text qualifiers). Today I ran into the opposite scenario. I needed to combine the different iterations of an array into a single string value.

Immediately my mind turned to extensions. A beautiful, simple and elegant solution.

Case for Using an Extension

Extensions allow you to add behavior to classes defined as Not Inheritable or Sealed.

In my example it wasn’t that I couldn’t inherit from the collection and override the behavior. The benefit of the extension was the automatic ability to support almost any object that implemented the IEnumerable interface. I could extend the interface without having to inherit from the interface or inherit from any implementors of the interface. Perfect!

Steps to Extend an Object

Extensions were introduced with VS.Net 2008. Be sure you are using the correct compile options when working with extensions.

To extend an interface, class or variable type requires the following actions:

  • Create a new sub or function with at least one parameter. The first parameter in the routine is of the type of interface, class or variable type to extend.
  • Next add the Extension attribute from the System.Runtime.CompilerServices namespace.

You should be aware that there are limitations to the extension. If the routine name is already in use the extension will be ignored. The error will not be flagged during compile but will cause a logic error during runtime.

Creating an IEnumerable Unifier

I began with a simple example…An array of strings.

I wanted to be able to combine the array of strings into a single string value using as little code as possible. The goal was to have a simple routine as follows:

Dim phoneList() As String = {"840-201-4910", "949-194-1999", "848-111-4919"}
Dim unifiedValue As String = phoneList.Collapse(","c)

The new Collapse routine has a single parameter defining the separator to use when concatenating the array values into a single string value. It’s clean, it’s simple and self-documenting!

And now for the simple code to make this routine work. We begin by creating a module called EnumerableExtensions to which we add a function named Collapse. The first argument will represent the object being extended, IEnumerable interface. The next function will accept a single argument defining what character to use to separate the combined values. Next we simply iterate thru the array adding the value if it exists. If the value doesn’t exist we continue to add the separator. The ToString method is called to handle an array of integers, characters, or other data types.

Imports System.Runtime.CompilerServices

Module EnumerableExtensions
  <Extension()> _
  Public Function Collapse(ByVal value As System.Collections.IEnumerable, ByVal separator As Char) As String
    Dim unitedValue As String = String.Empty

    For Each item As Object In value
      If item IsNot Nothing Then unitedValue &= item.ToString
      If separator IsNot Nothing Then unitedValue &= separator
    Next

    If unitedValue.EndsWith(seperator.ToString) Then unitedValue = unitedValue.Substring(0, unitedValue.Length - 1)
Return unitedValue
  End Function
End Module

Handling Text Qualifiers

What if we want to handle the scenario where our iteration values might contain the separator as part of the text? Begin by appending a text qualifier parameter to the routine. Use the new parameter to add the qualifier before and after the value. Take care to handle qualifiers contained within the value!

Imports System.Runtime.CompilerServices

Module EnumerableExtensions
  <Extension()> _
  Public Function Collapse(ByVal value As System.Collections.IEnumerable, ByVal separator As Char, ByVal qualifer As Char) As String
    Dim unitedValue As String = String.Empty

    For Each item As Object In value
      If qualifier IsNot Nothing Then unitedValue &= qualifier
      If item IsNot Nothing Then unitedValue &= item.ToString.Replace(qualifier, qualifier & qualifier)
      If qualifier IsNot Nothing Then unitedValue &= qualifier
      If separator IsNot Nothing Then unitedValue &= separator
    Next

    If unitedValue.EndsWith(seperator.ToString) Then unitedValue = unitedValue.Substring(0, unitedValue.Length - 1)

    Return unitedValue
  End Function
End Module

Uniting Object Values

Simple and effective. But we aren’t quite done yet. Let’s say we want to concatonate the values of a dictionary. The above code won’t work because value is of type DictionaryEntry. Calling the ToString method of the DictionaryEntry returns the class name type, “System.Collections.DictionaryEntry”. So our routine needs a little bit of work…

In the following code sample another parameter has been added that can be used to specify the property name that contains the value to contactonate. With this change we can support an array of classes.

While the modified routine is a bit more complex it remains fairly straight-forward. Reflection is used to get the value from the desired property. Please note that the property name must match perfectly. The property name is case-sensitive.

Imports System.Runtime.CompilerServices

Module EnumerableExtensions
  <Extension()> _
  Public Function Collapse( _
    ByVal value As System.Collections.IEnumerable, _
    ByVal separator As Char, _
    Optional ByVal qualifier As Char = Nothing, _
    Optional ByVal propertyName As String = Nothing _
  ) As String
    Dim unitedValue As String = String.Empty
    Dim instanceValue As Object

    For Each item As Object In value
      If propertyName IsNot Nothing AndAlso propertyName.Trim.Length > 0 Then
        Dim propertyItem As System.Reflection.PropertyInfo = item.GetType.GetProperty(propertyName)
        instanceValue = propertyItem.GetValue(item, Nothing)
      Else
        instanceValue = item
      End If

      If qualifier IsNot Nothing Then unitedValue &= qualifier
      If instanceValue IsNot Nothing Then unitedValue &= instanceValue.ToString.Replace(qualifier, qualifier & qualifier)
      If qualifier IsNot Nothing Then unitedValue &= qualifier
      If separator IsNot Nothing Then unitedValue &= separator
    Next

    If unitedValue.EndsWith(seperator.ToString) Then unitedValue = unitedValue.Substring(0, unitedValue.Length - 1)

    Return unitedValue
  End Function
End Module

To demonstrate that the code works as desired a simple test harness is provided below. Create a standard window console adding the following code to the main routine. Add the code from the previous sample into a separate module file in the same project. Run the application to view the results in the command console window.

Module Module1
  Sub Main()
    'Demonstrate Support of an ArrayList that Implements IEnumerable
    Dim nameList As New System.Collections.ArrayList
    nameList.Add("Joe")
    nameList.Add("Jane")
    nameList.Add("James")
    nameList.Add("Jennifer")
    System.Console.WriteLine(nameList.Collapse(","c, "'"c))
    System.Console.ReadKey()

    'Demonstrate Support of a Standard Array (Which Also Implements IEnumerable)
    Dim phoneList() As String = {"840-201-4910", "949-194-1999", "848-111-4919"}

    System.Console.WriteLine(phoneList.Collapse(","c, "'"c))
    System.Console.ReadKey()

    'Demonstrate Support of Sorted List (Which Supports IEnumerable)
    Dim namePhoneList As New System.Collections.SortedList
    namePhoneList.Add("Joe", "840-201-4910")
    namePhoneList.Add("Jane", "949-194-1999")
    namePhoneList.Add("James", "848-111-4919")

    'Note the next call won't work as desired because we failed to specify the property to get the value.
    System.Console.WriteLine(namePhoneList.Collapse(","c, "'"c))
    System.Console.ReadKey()

    'The following two unite calls work because the property name is specified (watch your casing).
    System.Console.WriteLine(namePhoneList.Collapse(","c, "'"c, "Key"))
    System.Console.WriteLine(namePhoneList.Collapse(","c, "'"c, "Value"))
    System.Console.ReadKey()
  End Sub
End Module

Advertisement

2 Comments »

  1. I simply want to tell you that I’m all new to blogs and actually enjoyed this website. Likely I’m planning to bookmark your blog . You actually have perfect writings. Kudos for sharing your web-site.

    Comment by Cleora Kinkead — February 9, 2011 @ 7:50 am | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: