If you've designed your configuration type to be serializable (marking the class with the [Serializable()] attribute) using external non-config xml files, text or database out will automatically persist any subtypes. Since XML Serialization is used for any storage mechanism other than the .NET config files persistance into these types will be automatic.
In order for this to work you have to either use types that support a TypeConverter natively (many .NET framework objects do this) or you can implement a custom TypeConverter that provides string serialization.
Here's an example of a custom type with a type converter implementation:
[TypeConverter(typeof(CustomCustomerTypeConverter)), Serializable()] public class CustomCustomer { public string Name = "Rick Strahl"; public string Company = "West Wind"; public decimal OrderTotal = 100.90M; } public class CustomCustomerTypeConverter : System.ComponentModel.ExpandableObjectConverter { /// <summary> /// Allow only string conversions /// </summary> /// <param name="context"></param> /// <param name="destinationType"></param> /// <returns></returns> public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string) ) return true; return base.CanConvertTo (context, destinationType); } /// <summary> /// Allow only string conversions /// </summary> /// <param name="context"></param> /// <param name="sourceType"></param> /// <returns></returns> public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string) ) return true; return base.CanConvertFrom (context, sourceType); } /// <summary> /// Convert into a string resource with a comma delimited list /// </summary> /// <param name="context"></param> /// <param name="culture"></param> /// <param name="value"></param> /// <param name="destinationType"></param> /// <returns></returns> public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { CustomCustomer Cust = (CustomCustomer) value; if ( destinationType == typeof(string) ) return Cust.Name + "," + Cust.Company + "," + string.Format( culture.NumberFormat,"{0}",Cust.OrderTotal); return base.ConvertTo(context,culture,value,destinationType); } /// <summary> /// Convert back into properties from a comma delimited list. Note the list is position specific. /// This code doesn't demonstrate proper error handling for manually invalidated values. /// </summary> /// <param name="context"></param> /// <param name="culture"></param> /// <param name="value"></param> /// <returns></returns> public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (! (value is string) ) return base.ConvertFrom (context, culture, value ); string Persisted = (string) value; CustomCustomer Cust = new CustomCustomer(); if (Persisted != null || Persisted != "") { string[] Fields = Persisted.Split(new char[1] {','}); Cust.Company = Fields[1]; Cust.Name = Fields[0]; Cust.OrderTotal = (decimal)wwUtils.StringToTypedValue(Fields[2],Cust.OrderTotal.GetType()); } return Cust; } }
Note that the ConvertFrom() method has no error checking. You probably will need to check and make sure you get the right number of returned fields and double check type values etc.
For more detail on TypeConverters please visit:
http://west-wind.com/weblog/posts/980.aspx