Click here to Skip to main content
13,259,976 members (50,298 online)
Click here to Skip to main content
Add your own
alternative version

Stats

4.7K views
40 downloads
4 bookmarked
Posted 9 Oct 2015

Make Your ConfigurationElementCollection Type-Explicit

Rate this:
Please Sign up or sign in to vote.
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:

// 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.

// 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.

// 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):

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:

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:

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!):

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:

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 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)

Share

About the Author

Robert Vandenberg Huang
Software Developer
Taiwan Taiwan
Software developer, video game enthusiast, drummer and also a huge Jazz music fan. Have been working on software development since junior high school. Love sharing knowledge with people, learning new things and having interaction with developer community.

My Homepage at GitHub

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionGreat Article Pin
eternalsoul21-Apr-16 22:25
membereternalsoul21-Apr-16 22: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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.171114.1 | Last Updated 9 Oct 2015
Article Copyright 2015 by Robert Vandenberg Huang
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid