DekGenius.com
[ Team LiB ] Previous Section Next Section

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.

    [ Team LiB ] Previous Section Next Section