Add "extension properties" to classes in C#

Extension methods allow you to add new methods to existing classes even if you don't have access to the classes' source code. For example, the Make an extension method that randomizes a two-dimensional array in C# example shows how to add an extension method to two-dimensional arrays holding data of an arbitrary type.

Unfortunately there is no way to make extension properties that add a property to an existing class. The usual solution, for controls at least, is to store extra values in an object's Tag property. That works but only for controls and other classes that have a Tag property. It also preempts the Tag property for other uses and only allows you to store only one value.

This example adds SetValue and GetValue extension methods to the object class to allow you to set and get any number of new "property" values.

The following code shows the ExtensionProperties class that adds the new extension methods.

public static class ExtensionProperties
{
    // Storage for the properties.
    private static Dictionary> PropertyValues =
               new Dictionary>();

    // Set a property value for the item.
    public static void SetValue(this object item, string name, object value)
    {
        // If we don't have a dictionary for this item yet, make one.
        if (!PropertyValues.ContainsKey(item))
            PropertyValues[item] = new Dictionary();

        // Set the value in the item's dictionary.
        PropertyValues[item][name] = value;
    }

    // Return a property value for the item.
    public static object GetValue(this object item, string name, object default_value)
    {
        // If we don't have a dictionary for
        // this item yet, return the default value.
        if (!PropertyValues.ContainsKey(item)) return default_value;

        // If the value isn't in the dictionary,
        // return the default value.
        if (!PropertyValues[item].ContainsKey(name)) return default_value;

        // Return the saved value.
        return PropertyValues[item][name];
    }

    // Remove the property.
    public static void RemoveValue(this object item, string name)
    {
        // If we don't have a dictionary for this item, do nothing.
        if (!PropertyValues.ContainsKey(item)) return;

        // If the value isn't in the dictionary, do nothing.
        if (!PropertyValues[item].ContainsKey(name)) return;

        // Remove the value.
        PropertyValues[item].Remove(name);

        // If the dictionary is empty, remove it.
        if (PropertyValues[item].Count == 0)
            PropertyValues.Remove(PropertyValues[item]);
    }

    // Remove the object's property dictionary.
    public static void RemoveAllValues(this object item)
    {
        // If we have a dictionary for this item, remove it.
        if (PropertyValues.ContainsKey(item))
            PropertyValues.Remove(PropertyValues[item]);
    }
}

The class starts by declaring a Dictionary named PropertyValues that uses objects for keys and other Dictionaries for values. It will look up an object in the top-level Dictionary to get the new property Dictionary for that object.

The "inner" Dictionary uses strings (the names of "properties") for keys and objects for values. Once the code finds an object's "inner" Dictionary, it uses it to look up values.

The SetValue method saves a named "property" value for an object. First it determines whether the object has an entry in the PropertyValues Dictionary. If there is no such Dictionary yet, the method adds it. The SetValue method then uses the new or existing Dictionary's index method to store the desired "property" value.

The GetValue method also starts by determining whether the PropertyValues Dictionary contains a dictionary for the object. If there is no such Dictionary, the method returns the default value passed to it as a parameter.

If the "inner" Dictionary does exist, the method determines whether it contains a value for the desired "property." If there is no such value, the method again returns the default value. Finally if the "inner" Dictionary contains a value for the desired "property," the method returns it.

The RemoveValue method removes a value from the Dictionaries. If the top-level PropertyValues Dictionary doesn't contain an entry for the object, the method does nothing. Finally if the "inner" Dictionary contains a value for the "property," the code removes it. Finally if the "inner" Dictionary is empty, the code removes it from the top-level PropertyValues Dictionary so it doesn't become cluttered with Dictionaries that are no longer needed.

Finally the RemoveAllValues method removes an object's "inner" Dictionary. Before you destroy an object to which you have assigned "properties" via these extension methods, you should delete all of its properties. If you don't, then they will continue to take up space in the Dictionaries. Even worse, the entries in the Dictionaries will hold references to the objects so they cannot be reclaimed by the garbage collector and that may cause a memory leak.

The following code shows how the program uses extension properties.

// Set the value for the txtName TextBox.
private void btnSetValue_Click(object sender, EventArgs e)
{
    txtName.SetValue(txtName.Text, txtValue.Text);
    txtValue.Clear();
}

// Get the value from the txtName TextBox.
private void btnGetValue_Click(object sender, EventArgs e)
{
    txtValue.Text = txtName.GetValue(txtName.Text, "").ToString();
}

// Remove the value from the txtName TextBox.
private void btnRemoveValue_Click(object sender, EventArgs e)
{
    txtName.RemoveValue(txtName.Text);
    txtValue.Clear();
}

When you click the Set Value button, the program calls the txtName TextBox's SetValue method passing it the name of the property and the value to assign to it.

When you click the Get Value button, the program calls the txtName TextBox's GetValue method passing it the name of the property and a default value, and displays the result.

When you click the Remove Value button, the program calls the txtName TextBox's RemoveValue method passing it the name of the property to remove.

The syntax isn't quite as simple as using a property but it's pretty easy.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.