HAHA! How’s that for a clever title?
Oh… well… ahem… nevermind.
As a avid user of my own INIFile class, which I first write about- at least it’s C# implementation- in my parsing INI files posting, I am always looking for ways to improve it’s usage make it more “accessible”.
Recently, I have been tasked (by way of my new title of “freelance consultant”) with creating several LOB (Line of Business) Type applications. Applications, naturally, have a tendency to lend their implementations to the creation and reading of settings. Being something of a fan of the simplicity of INI Files, I chose to use my INIFile class in the application. It works well, however, I have noticed that I have a lot of duplicate code. More specifically, I typically have to implement a “wrapper” class, which manages configuration information and reads/writes values to and from the INIFile as its own properties are accessed. For example:
1 2 3 4 5 6 7 8 9 10 |
public bool PopulateUserOrderDropdown { get { bool tparse; if(bool.TryParse(OurINI["Admin.Settings"]["PopulateUserOrderDropDown","false"].Value,out tparse) return tparse; return false; } set { OurINI["Admin.Settings"]["PopulateUserOrderDropDown"].Value;} } |
Nothing too dreadful, but imagine having nearly the exact same thing repeated a number of times! The code is repeated and as Larry Wall says, one of the traits of a good programmer is sloth. I don’t like having to write this same code over and over again! The INIFile is supposed to make it easy!
The trouble here stems from the fact that the INIFile values are only strings; and typically, many settings are represented in the application itself as integers and booleans, dates, and so forth. My first attempt to mitigate the clutter was a static method, which I called xParse:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static class boolEx { public static bool xParse(String Value, bool Default) { bool parseresult; if(bool.TryParse(Value,out parseresult)) return parseresult; else return Default; } } |
relatively straightforward- basically it’s a shell of what I had repeated over and over again. This mitigated the issue somewhat, so my properties in the wrapper looked like this:
1 2 3 4 5 |
public bool PopulateUserOrderDropdown { get { return boolEx.xParse(OurINI["Admin.Settings"]["PopulateUserOrderDropDown", "false"].Value); } set { OurINI["Admin.Settings"]["PopulateUserOrderDropDown"].Value;} } |
much more managable, but still, could we not make this more concise? My first thought, was that perhaps I could eliminate the necessity of having the wrapper at all; I recalled two interfaces from my old COM programming days, specifically, IDispatch and IDispatchEx. Surely, I could do something similar?
Unfortunately, the interfaces are for COM, and C# doesn’t have dynamics until Version 4.
So, I fired up Visual Studio 2010 express to see if I couldn’t add the dynamic language constructs to the INIFile class; additionally, since I still need to work with .NET 3.5, I’ll add the new code as a conditional compilation.
The first step was deciding exactly what I wanted to happen. Imagine code like this:
1 2 |
INIFile useINI = new INIFile("settings.ini"); String ConnectionString = (String)useINI.General.ConnectionString; |
The holy grail of the INIFile simplicity! Naturally, the .NET framework does provide the facility with which to add this functionality, as part of the System.Dynamic namespace.
The first step was deciding on the method by which to conditional compile. Since projects copy the source of a file to your project folder when you add them, it seemed reasonable to simply add it as a #define right inside the INIFile class itself.
1 |
#define CS4 |
And now, I just need to enclose all my new happy stuff in a conditional directive, and I’ll get the best of both worlds- C# 4.0 consumers who keep the #define will be able to use the suave new feature, and older consumers will still be able to work without ripping apart the classes. The code to add this was surprisingly simple; as it stands now the longest method (An implementation of TryDeleteMember, which is never called from C#/VB.NET consumers, so is excessive for my usage). First, obviously we enclose the import statement in the conditional compile; the class headers are conditionally compiled as well, only deriving from DynamicObject with CS4 set.
The core of the new functionality is in the overrides to the Dynamic Object’s TryGetMember.
For the INISection:
1 2 3 4 5 |
public override bool TryGetMember(GetMemberBinder binder,out object result) { result = this[binder.Name]; return true; } |
And for the INIFile…
1 2 3 4 5 |
public override bool TryGetMember(GetMemberBinder binder,out object result) { result = this[binder.Name]; return true; } |
Exactly the same, in fact. This works because of the indexer I added; the indexer will add the item if it doesn’t exist and return the new value, so even if the member name doesn’t exist, the INIFile will simply have that section added.
That’s for the retrieval of erements; to allow the assignment to them in the same fashion, we need to override TrySetMember(). In my case, this was a bit more involved, for flexibility purposes.
For example, code like INIFile.MainSection=”hello” should work, and change the name of the section. And why allow things like assignments from a Dictionary<String, String>, or maybe even a list (assigning a numbered id to set values)? And of course allow setting the Value directly, which will likely use the indexer much as I did for the TryGet… Implementations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
public override bool TrySetMember(SetMemberBinder binder, object value) { //if it is a dataitem, set it directly. if (value is INIDataItem) { this[binder.Name] = (INIDataItem)value; return true; } else if (value is Tuple<, Object>) { Tuple<, Object> theTuple = (Tuple<, Object>)value; INIDataItem getitem = this[binder.Name]; getitem.Name = theTuple.Item1; getitem.Value = theTuple.Item2.ToString(); return true; } else if (value is Tuple<, String>) { Tuple<, Object> theTuple = (Tuple<, Object>)value; INIDataItem getitem = this[binder.Name]; getitem.Name = theTuple.Item1; getitem.Value = theTuple.Item2.ToString(); return true; } else if (value is KeyValuePair<, Object>) { //Allow a KeyValuePair<,Object> to be passed to set Name and Value. KeyValuePair<, Object> castedval = (KeyValuePair<, Object>)value; INIDataItem getitem = this[binder.Name]; getitem.Name = castedval.Key; getitem.Value = castedval.Value.ToString(); return true; } else if (value is KeyValuePair<, String>) { //Allow a KeyValuePair<,String> to be passed to set Name and Value. KeyValuePair<, String> castedval = (KeyValuePair<, String>)value; INIDataItem getitem = this[binder.Name]; getitem.Name = castedval.Key; getitem.Value = castedval.Value; return true; } else { this[binder.Name].Value = value.ToString(); return true; } } |
setting the Value should be equally flexible; since we can, why not?
for example, why not make the following “legal”?
1 2 3 4 |
INIFile.Section.Value="newvalue"; INIFile.Section.Value=DateTime.Now; INIFile.Section.Value=Tuple.Create("NewName","Chicken"); INIFile.Section.Value=Tuple.Create("NewName",DateTime.Now); |
The first example sets the Value to a string, the second sets it to a DateTime that is silently casted to a String (using toString(), and the last two use the new C# 4.0 tuples, to set both the name of the value and the value simultaneously.
A more elegant solution would be to add this code to the Indexer, and merely call the indexer with the name and the value and return true if no exception occurs and false otherwise. However, I’m reluctant to go that route since some of the types are C# 4 types (Tuples).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public override bool TrySetMember(SetMemberBinder binder, object value) { if (value is String) { this[binder.Name].Name = (String)value; return true; } else if (value is List<INIItem>) { INISection getsection = this[binder.Name]; getsection.INIItems = (List<INIItem>)value; return true; } else { return false; } } |
So Now, I’ve got an INI File implementation that supports Dynamic invocation. Well, that’s great… except that the application I first found it clumsy in is using .NET 3.5, so I can’t use the dynamic features. Back at square one.
In C# 2008/3, we might not be able to leverage the power of dynamics, but we do have generics and Extension methods at our disposal. a feasible alternative could be to add a extension method to the INIDataItem class that has a generic type parameter that it will attempt to convert it’s string Value into. First, using ChangeType, second, it can try to invoke a static TryParse on the given Type to parse the “value” string. And if none of that works, it can return a passed in default. This is still more verbose than the dynamic solution, but it has two distinct advantages- first, it’s type-safe, so you get all the intellisense goodness, and second, it’s still shorter than the alternative.
Here is the code, which can be found in the cINIFile.cs file attached to this posting as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
public static class INItemValueExtensions { //extensions for INIDataItem //normally, INIDataItem is a Name/Value Pair; More Specifically, because of the way INI files are, they are //naturally typeless. However, most configuration options are mapped to a different type by the application. //and I've found it to be a gigantic pain to have to write the same TryParse() handling code over and over. //so I added these handy extensions to the INIDataItem class, which provide some functions for setting. //I keep them out of the main code simply because that way it doesn't clutter it up. It's already cluttered enough as-is. /// <summary> /// Attempts to use Convert.ChangeType() to change the Value of this INIDataItem to the specified type parameter. /// If this fails, it will attempt to call a static "TryParse(String, out T)" method on the generic type parameter. /// If THAT fails, it will return the passed in DefaultValue parameter. /// </summary> /// <typeparam name="T">Parameter Type to retrieve and act on in Static context.</typeparam> /// <param name="dataitem">INIDataItem instance whose value is to be parsed to the given type.</param> /// <param name="DefaultValue">Default value to return</param> /// <returns>Result of the parse/Conversion, or the passed in DefaultValue</returns> public static T GetValue<T>(this INIDataItem dataitem, T DefaultValue) { //Generic method, attempts to call a static "TryParse" argument on the given class type, passing in the dataitem's value. try { return (T)Convert.ChangeType(dataitem.Value, typeof(T)); } catch (InvalidCastException ece) { //attempt to call TryParse. on the static class type. //TryParse(String, out T) Type usetype = typeof(T); T result = default(T); Object[] passparams = new object[] { dataitem.Value, result }; try { bool tpresult = (bool)usetype.InvokeMember("TryParse", BindingFlags.Static, null, null, passparams); if (tpresult) { //tryparse succeeded! return (T)passparams[1]; //second index was out parameter... } } catch (Exception xx) { //curses... return DefaultValue; } } return DefaultValue; } /// <summary> /// Logical inverse of the getValue routine... a bit faster to implement... /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dataitem"></param> /// <param name="newvalue"></param> public static void setValue<T>(this INIDataItem dataitem, T newvalue) { dataitem.Value = newvalue.ToString(); } private static void GetTypeDefault<T>(out T result) { Type tt = typeof(T); //basic idea: call default, empty constructor using reflection. ConstructorInfo defaultconstructor = tt.GetConstructor(new Type[] { }); result = (T)defaultconstructor.Invoke(null); } } |
And there you have it, a bunch of awesome additions. INI files are often thought of as deprecated, but that’s only the INIFile functions. This class was designed because working with the registry makes it difficult to test properly, and because JSON,YAML, and many other formats are excessively complicated. when you just need a few basic settings, all you need is the clean, simple format of a INI file. And now, with these additions, code for reading from those INI files is clean and simple as well!
Have something to say about this post? Comment!