Click here to Skip to main content
15,886,857 members
Articles / Programming Languages / C#
Tip/Trick

Make Your ConfigurationElementCollection Type-Explicit

Rate me:
Please Sign up or sign in to vote.
4.60/5 (3 votes)
9 Oct 2015CPOL2 min read 17.8K   122   4   2
A short tip about how to enhance type definition of your ConfigurationElementCollection

Introduction

This is a simple but useful tip for people who need to create their customized configuration sections. When you are trying to provide your own section in App.config, a little problem, which possibly bothers you, is that the ConfigurationElementCollection class is not a type-explicit collection. Each element of enumeration you get is an object as it only implements the interface ICollection and IEnumerable. Although it can be casted with the extension method Enumerable.Cast<TResult>(), the InvalidCastException can still occur accidentally for other people who are not that familiar with the element’s actual type. The following tip will resolve the problem.

The Walkthrough

Let us say you have a GameConfigurationElement derived from ConfigurationElement as below:

C#
// The using section is omitted.

namespace Valve.Dota2.Configuration
{
    public class GameConfigurationElement : ConfigurationElement
    {
        #region Properties

        [ConfigurationProperty("type", IsRequired = true)]
        public string HeroName
        {
            get { return Convert.ToString(this["hero"]); }
            set { this["hero"] = value; }
        }

        [ConfigurationProperty("description")]
        public string Description
        {
            get { return Convert.ToString(this["description"]); }
            set { this["description"] = value; }
        }

        #endregion

        #region Constructor

        public GameConfigurationElement()
        {
            // Some initialization code here. 
        }

        #endregion
    }
} 

and you have a GameConfigurationElementCollection derived from ConfigurationElementCollection, representing the collection of GameConfigurationElement.

C#
// The using section is omitted.

namespace Valve.Dota2.Configuration
{
    public class GameConfigurationElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new GameConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as GameConfigurationElement).HeroName;
        }
    }
}

Then let us try to make our GameConfigurationElementCollection type-explicit. First of all, it shall implement IEnumerable<GameConfigurationElement> as each element within it is supposed to be GameConfigurationElement instance only.

C#
// The using section is omitted.

namespace Valve.Dota2.Configuration
{
    public class GameConfigurationElementCollection : ConfigurationElementCollection, 
        IEnumerable<GameConfigurationElement>
    {
        #region ConfigurationElementCollection Methods

        protected override ConfigurationElement CreateNewElement()
        {
            return new GameConfigurationElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as GameConfigurationElement).HeroName;
        }

        #endregion

        #region IEnumerable<GameConfigurationElement> Methods

        public new IEnumerator<GameConfigurationElement> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

How do we implement the IEnumerator<GameConfigurationElement> GetEnumerator() method? One of the solutions you may think of is probably like this (at least I did before):

C#
public new IEnumerator<GameConfigurationElement> GetEnumerator()
{
    return this.Cast<GameConfigurationElement>().GetEnumerator();
    // Or:
    // return this.AsEnumerable<GameConfigurationElement>().GetEnumerator();
}

But the only outcome you will get during the enumeration (for instance, a foreach loop) is:

Image 1

How come? Because both Enumerable.Cast<TResult>() and Enumerable.AsEnumerable<TResult>() extensions will try to call IEnumerator<TResult> GetEnumerator() to get the actual result. Since your collection has implemented the interface, it will result in StackOverflowException whenever you try to cast your collection to specified type within the method itself.

To get rid of this exception, we need to make a detour to avoid casting the collection directly. Here is one of the feasible solutions by utilizing Enumerable.Range and ConfigurationElementCollection.BaseGet(Int32) method to Select each element individually. After that, we "yield" the loop, as below:

C#
public new IEnumerator<GameConfigurationElement> GetEnumerator()
{
    // Do not do this:
    // return this.Cast<GameConfigurationElement>().GetEnumerator();
    //
    // Nor this:
    // return this.AsEnumerable<GameConfigurationElement>().GetEnumerator();
    //
    // Do this:
    foreach (GameConfigurationElement element in
        Enumerable.Range(0, base.Count).Select(base.BaseGet))
            yield return element;
}

so the problem gets resolved. We are now able to safely enumerate the element of our collection (and of course, it is type-explicit!):

C#
using System;
using System.Configuration;

namespace Valve.Dota2.Configuration.Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            GameConfigurationSection section = 
                ConfigurationManager.GetSection("valve.dota2") as GameConfigurationSection;

            foreach (var hero in section.Games)
            {
                Console.WriteLine("Hero Name: {0}", hero.HeroName);
                Console.WriteLine("Hero Description: {0}", hero.Description);
                Console.WriteLine();
            }
            Console.ReadKey(true);
        }
    }
}

The GameConfigurationSection class can be seen below:

C#
using System.Collections.Generic;
using System.Configuration;

namespace Valve.Dota2.Configuration
{
    public class GameConfigurationSection : ConfigurationSection
    {
        [ConfigurationProperty("games")]
        [ConfigurationCollection(typeof(GameConfigurationElementCollection), 
            AddItemName = "add", 
            ClearItemsName = "clear", 
            RemoveItemName = "remove")]
        public GameConfigurationElementCollection Games
        {
            get { return this["games"] as GameConfigurationElementCollection; }
            set { this["games"] = value; }
        }
    }
}

and here is our App.confg.

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="valve.dota2" 
             type="Valve.Dota2.Configuration.GameConfigurationSection, Valve.Dota2.Configuration"/>
  </configSections>
  <valve.dota2>
    <games>
      <add hero="Invoker" description="No more midas!"></add>
      <add hero="Techies" description="Deserves to be nerfed."></add>
      <add hero="Templar Assasin" description="My waifu."></add>
    </games>
  </valve.dota2>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

References

History

  • 2015-10-09 Initial post
  • 2015-10-09 Added attachment link, GameConfigurationSection and App.config detail

License

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


Written By
Software Developer
Taiwan Taiwan
Back-end developer, English learner, drummer, game addict, Jazz fan, author of LINQ to A*

Comments and Discussions

 
QuestionThank you, great article Pin
Member 1524541614-Jun-21 1:34
Member 1524541614-Jun-21 1:34 
QuestionGreat Article Pin
eternalsoul21-Apr-16 21:25
eternalsoul21-Apr-16 21:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.