Recipe 12.10 Controlling Additions to an ArrayList Through Attributes
Problem
You need to allow only certain
types of items to be stored within an ArrayList or
an ArrayList-derived container, and it is likely
that more types will be added to this list of allowed types in the
future. You need the added flexibility to mark the types that are
allowed to be stored in the ArrayList as such,
rather than changing the container.
Solution
First, create a new attribute to mark the types that are allowed to
be stored in the ArrayList. The following code
defines the AllowedInListAttribute attribute:
using System;
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Struct |
AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class AllowedInListAttribute : Attribute
{
public AllowedInListAttribute( ) {}
public AllowedInListAttribute(bool allow)
{
allowed = allow;
}
// by defaulting to false, we default the usage to preventing the
// use of the type.
private bool allowed = false;
public bool IsAllowed
{
get {return (allowed);}
set {allowed = value;}
}
}
Next, mark all classes that can be stored in the
ArrayList with this attribute:
[AllowedInListAttribute(true)]
public class ItemAllowed {}
[AllowedInListAttribute(true)]
public class Item {}
In addition, this attribute allows types to be disallowed from being
stored in the ArrayList by passing
false to its constructor or by not passing an
argument to the attribute constructor or not applying the attribute:
[AllowedInListAttribute(false)]
public class ItemDisallowed {}
public class ItemUnmarked {}
Types not marked with this attribute enabled will not be added to the
FilteredArrayList shown next.
Finally, create a new class that inherits from
ArrayList and overrides both the indexer and
Add members. The overridden methods will check the
object being added to determine whether it is marked with the
AllowedInListAttribute attribute. If it is, and if
the IsAllowed property returns
true, this object is added to the subclassed
ArrayList:
public class FilteredArrayList : ArrayList
{
private string attributeTypeName =
"CSharpRecipes.Reflection+AllowedInListAttribute";
public override object this[int index]
{
get
{
return (base[index]);
}
set
{
object[] allowedAttrs = value.GetType( ).GetCustomAttributes(
Type.GetType(attributeTypeName), false);
if (allowedAttrs.Length > 0)
{
if (((AllowedInListAttribute)allowedAttrs[0]).IsAllowed
== true)
{
base[index] = value;
return;
}
}
throw (new ArgumentException("Type cannot be added to this list",
"obj"));
}
}
public override int Add (object obj)
{
object[] allowedAttrs = obj.GetType( ).GetCustomAttributes(
Type.GetType(attributeTypeName), false);
if (allowedAttrs.Length > 0)
{
if (((AllowedInListAttribute)allowedAttrs[0]).IsAllowed == true)
{
return (base.Add(obj));
}
}
throw (new ArgumentException("Type cannot be added to this list",
"obj"));
}
}
You can then instantiate an ArrayList object and
add only allowed elements to it, as the following code illustrates:
using System;
using System.Collections;
using System.Reflection;
public static void GuardArrayList( )
{
FilteredArrayList list = new FilteredArrayList( );
AddToArray(list, new ItemAllowed( ) );
AddToArray(list, new ItemDisallowed( ) );
AddToArray(list, new ItemUnmarked( ) );
AddToArray(list, new Item( ) );
Console.WriteLine("ArrayList contains " + list.Count + " items.");
}
private static void AddToArray(FilteredArrayList fa, object obj)
{
try
{
fa.Add(obj);
}
catch (ArgumentException e)
{
Console.WriteLine("Unable to add " + obj.ToString( )
+ "\n " + e.Message);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
This code produces the following output:
Unable to add CSharpRecipes.Reflection+ItemDisallowed
Type cannot be added to this list
Parameter name: obj
Unable to add CSharpRecipes.Reflection+ItemUnmarked
Type cannot be added to this list
Parameter name: obj
ArrayList contains 2 items.
Discussion
There are times, especially during the initial phases of coding,
where the code is constantly changing along with the design specs.
This can make it especially difficult to keep your code stable and
bug-free. We can alleviate some of these problems by using attributes
to mark types in a special way. It is usually easier and much cleaner
to add or remove attributes than to add or remove code from within
the type.
This recipe uses a custom attribute,
AllowedInListAttribute, to mark certain types as
allowed to be stored in an ArrayList. This
attribute may be placed on types such as interfaces, structures, or
classes. (The targets of an attribute are defined by which members of
the AttributeTargets enumeration are passed as
arguments to the AttributeUsage
attribute's constructor.) It has one private field,
Allowed, which is used to determine if the type it
marks is able to be stored in an ArrayList
(true) or not (false).
To use this attribute, subclass ArrayList and
override both the indexer and Add method of the
ArrayList class. The get
accessor of the indexer can simply pass the call through to the base
class get accessor, since we're
interested in filtering items only as they go into the list. However,
the set accessor needs to test for the existence
of the AllowedinListAttribute on the type of the
object being added to the FilteredArrayList. If it
does not exist, the object may not be added. If this attribute
exists, but the IsAllowed property returns
false, it is still not allowed to be added to this
slot. Only when this attribute exists and the
IsAllowed property returns true
can this object be added to this ArrayList.
The set accessor of the indexer and the
Add method both throw an exception if an object
cannot be added to the ArrayList.
See Also
See the "Attribute Class" topic in
the MSDN documentation.
|