Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Extension Methods for Multiple Control Tags

4.85/5 (7 votes)
28 Oct 2018CPOL2 min read 5K  
The Control.Tag property is meant to contain data closely related to the control. Let's extend it.

The objective of the extension methods provided in this tip is to facilitate the storage, manipulation, and retrieval of multiple values in the Tag property of a Control object.

The Code

The extension methods InitTag, GetTag, and SetTag are implemented as shown below:

C#
internal static void InitTag( this Control control, params object[] objects )
{
    control.Tag = objects;
}

internal static object GetTag( this Control control, int index )
{
    if( control.Tag.GetType() != typeof( object[] ) )
    {
        return null;
    }

    var objects = control.Tag as object[];

    if( index < 0
        || index >= objects.Length )
    {
        return null;
    }

    return objects[ index ];
}

internal static void SetTag( this Control control, int index, object value )
{
    if( control.Tag.GetType() != typeof( object[] ) )
    {
        return;
    }

    var objects = control.Tag as object[];

    if( index < 0
        || index >= objects.Length )
    {
        return;
    }

    objects[ index ] = value;
}

Their use is as follows:

C#
listview1.InitTag( SortOrder.Ascending, @"c:\Program Files (x86)\" );

var sortOrder = listview1.GetTag( 0 ) as SortOrder;
var path      = listview1.GetTag( 1 ) as string;

listview1.SetTag( 1, @"c:\Program Files\" );

Godspeed.

The Rant

The Tag property of the Control class is often used to store individual values.

C#
listview1.Tag = @"c:\Program Files (x86)\";
listview2.Tag = SortOrder.Ascending;

It is also used to store (references to) objects.

C#
class Chair
{
    public int  legs;
    public bool backrest;
}

var chair = new Chair();

chair.legs     = 3;
chair.backrest = false;

textbox1.Tag   = chair;

Retrieving the value from the Tag property nearly always involves a cast since the Tag property is of type Object.

C#
var path      = listview1.Tag as string;
var sortOrder = listview2.Tag as SortOrder;
var chair     = textbox1.Tag as Chair;

In order to store multiple values or objects in a single Tag, a collection is required.

C#
var integers = new List<int>(){ 0, 1, 2, 3, 4 };

listbox.Tag  = integers;

var numbers  = listbox.Tag as List<int>;

Often times, the collection itself contains values and objects of various types. This, in turn, requires additional casts.

C#
listview1.Tag = new object[]{ 0, "tin", false };

var bucket    = listview1.Tag as object[];

var holes     = bucket[ 0 ] as int;
var material  = bucket[ 1 ] as string;
var leaks     = bucket[ 2 ] as bool;

bucket[ 2 ]   = true;

Using the extension methods provided at the top of the page, the code above becomes the code below:

C#
listview1.InitTag( 0, "tin", false );

var holes    = listview1.GetTag( 0 ) as int;
var material = listview1.GetTag( 1 ) as string;
var leaks    = listview1.GetTag( 2 ) as bool;

listview1.SetTag( 2, true );

A bit neater.

Caveats

Potential misuse is all too easy. One wrong cast can quickly upset the state of an otherwise pretty program. This is a necessary "fault" of the Tag property itself, one that naturally increases in complexity when storing multiple values.

Considerations and Alternative Implementations

I prefer to use an object[] and thus state in the InitTag method the amount and the type of values the Tag is to contain. This is the simplest approach, in my view, as it makes remembering the index (and type) of each element easier, thus minimizing somewhat unexpected misuse.

However, interesting alternatives exist.

Rewriting these extension methods to use a List<object> instead of an object[] adds flexibility, albeit at the cost of reliability. Remembering the index (and type) of each possibly changing element can become cumbersome.

A Dictionary can also be used, with Keys perhaps being strings or enums. This approach circumvents the indexing problem and allows for very large collections of items. The problem now becomes one of remembering the Keys. Although enums go a long way to safeguard against a weak memory, the casting issue remains.

The casting issue can be more or less addressed via a parallel dictionary (containing the type of each element) or multiple extensions methods (requesting each type explicitly with a prayer). However, I shun these approaches because the amount of additional code required makes the use of the Tag property overly cumbersome.

Kudos

I was motivated to share my code after reading this tip by . Check it out.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)