Playing with fire simulating extension members via Extension Methods by ThinqLinq

Playing with fire simulating extension members via Extension Methods

As I was listening to Scott Hanselman's Hanselminutes episode on LINQ to XML, he made a statement insinuating that you could add stateful behavior to objects via extension methods. As I was adding a comment stating that it was not possible, I started thinking and figured out a way. I DO NOT RECOMMEND DOING THIS. It is possible, but will not scale. That being said, here's a possible implementation to set and retrieve a MiddleName value to a Person object that does not have a middle name property defined.

DON'T DO THIS AT HOME!!! (you have been warned)

First the class definition for my person. It will have a FirstName and LastName and a read-only Guid to uniquely identify the instance. I use the following definition only for sake of example. You should not expose fields outside of your classes.

Public Class Person
    Public ReadOnly Id As Guid = Guid.NewGuid
    Public FirstName As String = ""
    Public LastName As String = ""
End Class

Next, we can create a couple extension methods. I'll use the VB syntax. Naturally, the same can be done in C# 3.0 as well. To create an extension method, we decorate a Module with the Extension attribute which resides in the System.Runtime.CompilerServices namespace.

<Extension()> _
Module Extension

We then set-up a private dictionary of values that we will set and retrieve based on the Guid exposed by the person class.

   Private _Names As New Dictionary(Of Guid, String)

The extension attribute can not be applied to a Property, thus we set up separate methods for the Set and Get portions. Each method will be an extension method as indicated by the custom attribute. The first parameter of each method will need to be the instance of the type that we are extending. Thus, we can set a value in our dictionary using the following method:

    <Extension()> _
    Public Sub SetMiddleName(ByVal instance As Person, ByVal value As String)
        If _Names.Keys.Contains(instance.Id) Then
            _Names(instance.Id) = value
            _Names.Add(instance.Id, value)
        End If
    End Sub

Likewise, we can retrieve the value from the dictionary using the following function:

    <Extension()> _
    Public Function GetMiddleName(ByVal instance As Person) As String
        If _Names.Keys.Contains(instance.Id) Then
            Return _Names(instance.Id)
            Return String.Empty
        End If
    End Function

Ok, now we are ready to consume our classes and the new extension method. We'll begin by creating a list of people that we will add the authors of our LinqInAction book.

Sub Main()
    Dim People as new List(of Person)

To this list, we can add person instances. Using the new object initializer, we can set the public fields easily. Unfortunately, we have to take a separate step to set the new extension "property":

        Dim myself As Person = New Person With {.FirstName = "Jim", .LastName = "Wooley"}

Note that although the Person type didn't have a method called SetMiddleName, we can consume it as long as the extension method is in scope. We can add the other authors and output the results as follows:

        Dim Author2 As Person = New Person With {.FirstName = "Steve", .LastName = "Eichert"}

        For Each item In people
            Console.WriteLine(item.FirstName & " " & item.GetMiddleName & " " & item.LastName)
    End Sub

If we run this code, the following output results:

Jim Bruce Wooley
Fabrice Unknown Marguire
Steve  Eichert

Since we pushed the values into the private dictionary in our extension class, we were able to set different values for each instance based on the ID (or you could use the GetHashCode).

Mission accomplished. What's wrong with the code? The biggest problem is that it will not scale! Since the dictionary lives for the life of the appdomain, it will constantly eat up memory as more values are set and never let go of it until your application has closed. Because of this, I'm not even going to speculate on when you might actually want to use it.

So, why did I do this? Why does any curious programmer do stuff? Because we can. Please don't use this code. If you do, don't tell anyone I told you how to do it ;-)

Posted on - Comment
Categories: VB -
comments powered by Disqus