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:
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:
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.
listview1.Tag = @"c:\Program Files (x86)\";
listview2.Tag = SortOrder.Ascending;
It is also used to store (references to) objects.
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
.
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.
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.
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:
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 Shahin Khorshidnia. Check it out.