ADOConnection ADOConnection - 7 months ago 40
C# Question

StackOverflowException for nestes configuration elements

Im trying to make configuration with nested elemets.
but parent-child relation is one-to-one, not one-to-many.
Meaning storageProvider could have only one nestedProvider.

Nesting level is not limited.

...
<store storeName="123">
<storageProvider type="disk">
<nestedProvider type="share">
<nestedProvider type="amazon-s3">
</nestedProvider>
</nestedProvider>
</storageProvider>
</store>
...


Question
When I create StorageProviderElement with NestedProvider property and try to read configuration I catch StackOverflowException in mscorlib. Like there is a bug in .NET (im using .NET 4.5)

enter image description here

Am I doing something wrong or it is expected behavior?

At this point I had to change this property to a collection (like you do in any other way) but still I wonder why I can't make nested one-to-one elements.

Code:
StoreElement

public class StoreElement : ConfigurationElement
{
private const string storeName = "storeName";
private const string storageProvider = "storageProvider";

[ConfigurationProperty(storeName, IsKey = true, IsRequired = true)]
public string StoreName
{
get
{
return (string)base[storeName];
}
}

[ConfigurationProperty(storageProvider, IsRequired = true)]
public StorageProviderElement StorageProvider
{
get
{
return (StorageProviderElement)this[storageProvider];
}
}
}


StorageProviderElement
(this is the one with recursion)

public class StorageProviderElement : ConfigurationElement
{
private const string type = "type";
private const string options = "options";
private const string nestedProvider = "nestedProvider";


[ConfigurationProperty(type, IsRequired = true)]
public string Type
{
get
{
return (string)base[type];
}
}

[ConfigurationProperty(options, IsDefaultCollection = false, IsRequired = false)]
public GenericConfigurationElementCollection<StorageProviderOptionElement> Options
{
get
{
return (GenericConfigurationElementCollection<StorageProviderOptionElement>) this[options];
}
}

// this is what trigger stack overflow exception
[ConfigurationProperty(nestedProvider, IsDefaultCollection = false, IsRequired = false)]
public StorageProviderElement NestedProvider
{
get
{
return (StorageProviderElement)this[nestedProvider];
}
}
}


UPDATE:
Capitan obvious to the rescue.
Educating screenshot for over educated downvoting CTOs:

YOU CAN NOT SEE STACK EASEYLLY.

enter image description here

Evk Evk
Answer Source

The source of this exception is this method of ConfigurationElement:

private static ConfigurationProperty CreateConfigurationPropertyFromAttributes(PropertyInfo propertyInformation)
{
  ConfigurationProperty configurationProperty = (ConfigurationProperty) null;
  if (Attribute.GetCustomAttribute((MemberInfo) propertyInformation, typeof (ConfigurationPropertyAttribute)) is ConfigurationPropertyAttribute)
    configurationProperty = new ConfigurationProperty(propertyInformation);
  if (configurationProperty != null && typeof (ConfigurationElement).IsAssignableFrom(configurationProperty.Type))
  {
    ConfigurationPropertyCollection result = (ConfigurationPropertyCollection) null;
    ConfigurationElement.PropertiesFromType(configurationProperty.Type, out result);
  }
  return configurationProperty;
}

It checks if there is ConfigurationProperty attribute on given property and if yes and property type inherits from ConfigurationElement (your case) - it recursively inspects that property type again. If property type is the same as outer class type - recursion never ends and causes stackoverflow exception.

So in short - you cannot do this (will throw stackoverflow immediately when you try to get corresponding section, without actually calling any of your methods):

public class StorageProviderElement : ConfigurationElement
{      
    [ConfigurationProperty("whatever")]        
    public StorageProviderElement Whatever
    {
        get;
    }
}

Looks like a bug to me indeed, not sure, maybe there is some valid reasoning behind that, but I cannot find any.

Short example to reproduce:

class Program {
    static void Main(string[] args) {
        // throws
        ConfigurationManager.GetSection("store");                        
    }
}

public class StoreElement : ConfigurationSection
{
    [ConfigurationProperty("storageProvider")]
    public StorageProviderElement StorageProvider { get; }
}

public class StorageProviderElement : ConfigurationElement {
    [ConfigurationProperty("whatever")]
    public StorageProviderElement Whatever { get; }
}

In app.config

<configSections>    
  <section name="store" type="ConsoleApp4.StoreElement, ConsoleApp4"/>
</configSections>
<store />