Wednesday, 9 September 2009

Generic configuration element collection

Tiered of endlessly writing your custom ConfigurationElementCollection classes and duplicating your code. Well so was I which is why I ended up abstracting out the core functionality into an abstract base class. This is a similar approach to the one Richard Adleta  used in his post on A generic ConfigurationElementCollection implementation but with some differences.

public abstract class BaseConfigurationElementCollection<TConfigurationElementType> : ConfigurationElementCollection, IList<TConfigurationElementType> where TConfigurationElementType : ConfigurationElement, IConfigurationElementCollectionElement, new()
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new TConfigurationElementType();
    }
 
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((TConfigurationElementType)element).ElementKey;
    }
 
    #region Implementation of IEnumerable<TConfigurationElementType>
 
    IEnumerator<TConfigurationElement> IEnumerable<TConfigurationElement>.GetEnumerator()
    {
        foreach (TConfigurationElement type in this)
        {
            yield return type;
        }
    }
 
    #endregion
 
    #region Implementation of ICollection<TConfigurationElementType>
 
    public void Add(TConfigurationElementType configurationElement)
    {
        BaseAdd(configurationElement, true);
    }
 
    public void Clear()
    {
        BaseClear();
    }
 
    public bool Contains(TConfigurationElementType configurationElement)
    {
        return !(IndexOf(configurationElement) < 0);
    }
 
    public void CopyTo(TConfigurationElementType[] array, int arrayIndex)
    {
        base.CopyTo(array, arrayIndex);
    }
 
    public bool Remove(TConfigurationElementType configurationElement)
    {
        BaseRemove(GetElementKey(configurationElement));
 
        return true;
    }
 
    bool ICollection<TConfigurationElementType>.IsReadOnly
    {
        get { return IsReadOnly(); }
    }
 
    #endregion
 
    #region Implementation of IList<TConfigurationElementType>
 
    public int IndexOf(TConfigurationElementType configurationElement)
    {
        return BaseIndexOf(configurationElement);
    }
 
    public void Insert(int index, TConfigurationElementType configurationElement)
    {
        BaseAdd(index, configurationElement);
    }
 
    public void RemoveAt(int index)
    {
        BaseRemoveAt(index);
    }
 
    public TConfigurationElementType this[int index]
    {
        get
        {
            return (TConfigurationElementType)BaseGet(index);
        }
        set
        {
            if (BaseGet(index) != null)
            {
                BaseRemoveAt(index);
            }
            BaseAdd(index, value);
        }
    }
 
    #endregion
}

The difference between mine and Richard’s post is that my class inherits from IList<T>  and I don’t override the CollectionType property on the base class. I felt this should be done at the implementation as you also need to define the collection type in the ConfigurationCollection attribute.

The one thing that I hadn’t thought of and I borrowed from Richard was to make the generic type be an implementation of an interface for getting the elements key, in my example this is the IConfigurationElementCollectionElement interface.

Now when you want to create a configuration collection you are just left with defining the ConfigurationCollection attribute and the CollectionType and ElementName methods.

[ConfigurationCollection(typeof(CustomConfigurationElement), AddItemName = CONST_ELEMENT_NAME, CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class CustomConfigurationCollection : BaseConfigurationElementCollection<CustomConfigurationElement>
{
    #region Constants
    private const string CONST_ELEMENT_NAME = "Custom";
    #endregion
 
    public override ConfigurationElementCollectionType CollectionType
    {
        get
        {
            return ConfigurationElementCollectionType.BasicMap;
        }
    }
 
    protected override string ElementName
    {
        get { return CONST_ELEMENT_NAME; }
    }
}

Happy configuring.

Edit:
Thanks to BozoJoe and britman for pointing out that my initial implementation of the IEnumerable<>GetEnumerator() was broken and they were getting a StackOverflow which was because it was recursively calling itself . I have now updated the post to fix this.

7 comments:

  1. Great stuff. However, if I use a linq expression against the collection the "IEnumerator IEnumerable.GetEnumerator()" blows up with a StackOverflow.

    ReplyDelete
  2. Yes - great stuff. I had the same problem as BozoJoe (StackOverflow with the enumerator) but rewriting GetEnumerator() like this solved it (I am sure there are better ways...):
    IEnumerator IEnumerable.GetEnumerator() {
    IEnumerable ie = (this as System.Collections.IEnumerable).OfType();
    return ie.GetEnumerator();
    }

    ReplyDelete
  3. Thanks for the response. I came accross this too but didn't update the post. You can do this instead.

    IEnumerator IEnumerable.GetEnumerator()
    {
    foreach (TConfigurationElement type in this)
    {
    yield return type;
    }
    }

    I will now updated the original post.

    ReplyDelete
  4. This will cause StackOverflowException. Use in (IEnumerable)this instead

    ReplyDelete
  5. HI,

    I cannot find the IConfigurationElementCollectionElement interface, would you please post it ?
    Is it like bellow ?

    public interface IConfigurationElementCollectionElement
    {
    public object ElementKey { get; }
    }

    ReplyDelete
  6. Hi,
    Another problem is the following compiled error, Thanks you.
    --> Cannot find type 'TConfigurationElement'

    IEnumerator IEnumerable.GetEnumerator()
    {
    foreach (TConfigurationElement type in this)
    {
    yield return type;
    }
    }

    ReplyDelete
  7. Since it took me a while to comprehend and get a working example, for convenience here is the complete .cs file
    http://textuploader.com/6og9 and the example:

    public class MRUElement : ConfigurationElement, IConfigurationElementCollectionElement
    {
    public MRUElement() { }

    [ConfigurationProperty("MRU", IsKey = true, DefaultValue = "(none)")]
    public string ElementKey
    {
    get { return (string)this["MRU"]; }
    set { this["MRU"] = value; }
    }
    }

    [ConfigurationCollection(typeof(MRUElement), AddItemName = CONST_ELEMENT_NAME, CollectionType = ConfigurationElementCollectionType.BasicMap)]
    public class CustomConfigurationCollection : BaseConfigurationElementCollection
    {
    #region Constants
    private const string CONST_ELEMENT_NAME = "name";
    #endregion

    public override ConfigurationElementCollectionType CollectionType
    {
    get { return ConfigurationElementCollectionType.BasicMap; }
    }

    protected override string ElementName
    {
    get { return CONST_ELEMENT_NAME; }
    }
    }

    ReplyDelete