.NET/C#: refactoring some C# 1 code that uses HashTables as a poor mans property bag (via:Stack Overflow)
Posted by jpluimers on 2014/01/30
A while ago, I was refactoring some C# 1 code that uses HashTables as a poor mans property bag.
The problem was that I felt my code was convoluted, and should be denser, especially avoiding Convert.ChangeType. My code was already much simpler than casting tuples to a superclass.
So I asked this question on StackOverflow: c# – Is there a solution that feels less clumsy than Convert.ChangeType to get the value from a HashTable – Stack Overflow.
User dasblinkenlight showed it could be shortened and explained why (hyperlinks are mine):
Since System.String is sealed, the expression
genericType.IsSubclassOf(stringType)
is the same as
genericType == stringType
Therefore you do not need a call of Convert.ChangeType: you can cast to T by casting to object, like this:
object stringResult; // Note the change of type to "object" if (haveValue) stringResult = ((string)properties[propertyName]).Trim(); else stringResult = string.Empty; result = (T)stringResult; // It is allowed to cast object to generic T
The original .NET 1.1 code had loads of null checks wrapped if/then/else statements to assign default values for null values.
I wanted to get rid of that, and get code like this:
using System; using System.Collections; using System.Collections.Generic; using BeSharp.Collections; using BeSharp.Generic; namespace HashTableExtensionsDemo { class Program { static void Main(string[] args) { Hashtable properties = new Hashtable(); properties["int"] = 21; properties["bool"] = false; properties["double"] = 3.14; properties["string"] = "foo"; properties["null"] = null; // get values that are not null int i = properties.LoadValue("int"); bool b = properties.LoadValue("bool"); double d = properties.LoadValue("double"); string s = properties.LoadValue("string"); printValues(i, d, s); // convert null values to a specified default i = properties.LoadValue("null", -i); b = properties.LoadValue("bool", !b); d = properties.LoadValue("null", -d); s = properties.LoadValue("null", "empty"); printValues(i, d, s); // convert null values to the default for the type i = properties.LoadValue("null"); b = properties.LoadValue("bool"); d = properties.LoadValue("null"); s = properties.LoadValue("null"); printValues(i, d, s); } private static void printValues(int i, double f, string s) { IList nameSeparatorValues = Reflector.GetNameSeparatorValues(new { i, f, s }); Console.WriteLine(string.Join(Environment.NewLine, nameSeparatorValues)); } } }
The demo code has this output, showing that it works in three cases:
- property has a value
- property is null, default is applied
- property is null, default(T) is applied
i:21
f:3.14
s:foo
i:-21
f:-3.14
s:empty
i:0
f:0
s:
I changed the code since the StackOverflow question to make it clear.
It now consists of three methods:
- A generic
T LoadValue
without a default; it will give you the default(T) when the obtained property is null. - A specific
string LoadValue
where you specify what happens with a null value: choose between returning a null string or String.Empty. - A generic
T LoadValue
where you can pass your own default value that is returned when the obtained property is null.
Enjoy (:
–jeroen
using System; using System.Collections; namespace BeSharp.Collections { public static class HashTableExtensions { public static T LoadValue(this Hashtable properties, string propertyName) { T defaultValue = default(T); T result = LoadValue(properties, propertyName, defaultValue); return result; } public static string LoadValue(this Hashtable properties, string propertyName, bool nullStringAllowed) { string defaultValue; if (nullStringAllowed) defaultValue = null; else defaultValue = string.Empty; string result = LoadValue(properties, propertyName, defaultValue); return result; } public static T LoadValue(this Hashtable properties, string propertyName, T defaultValue) { T result; bool haveValue = properties[propertyName] != null; Type genericType = typeof(T); Type stringType = typeof(string); if (genericType == stringType) { object stringResult; if (haveValue) stringResult = ((string)properties[propertyName]).Trim(); else stringResult = defaultValue; result = (T)stringResult; } else { if (haveValue) result = (T)(properties[propertyName]); else result = defaultValue; } return result; } } }
–jeroen
Leave a Reply