I am stuck with some old API that involves COM and ODBC, and I cannot switch to cool things like MEF or entity framework. So, I am working on some wrappers that will help me write less code and also bring some of the type safety back (as you know, COM and SQL do not have type safety, at least not in the same way as C# does).
The goal is that I want to be able to write things like
int? orderId = sqlHelper.GetOrderId(...some arguments that will help the where clause...)
and
List<int> goodOrderIds = sqlHelper.GetGoodOrderIds(...)
and
List<int?> orderId = activeReport.GetGoodSelectedOrderIds()
where activeReport
is a home-brewed GUI grid, which may or may not have a column named order_id
, and if it does, then some rows of the GUI report may still have null values.
Here is an extension class for the GUI report. I have two TODO comments that I would like you to address:
public static class ReportExtensions
{
public static IList<object> GetSelectedFieldValues(this Report activeReport, string fieldName)
{
// Implementation hidden. May return at least some null values.
return result;
}
public static IList<T> GetSelectedFieldValues<T>(this Report activeReport, string fieldName)
where T : struct, IConvertible
{
// This smells like a maybe monad; I wish I got Haskell.
// TODO: See if this can be simplified.
return activeReport.GetSelectedFieldValues(fieldName)
.Select(value => GenericConverter.To<T>(value))
.Where(nullable => nullable.HasValue)
.Select(nullable => nullable.Value)
.ToList();
}
public static IList<string> GetSelectedStringFields(this Report activeReport, string fieldName)
{
// TODO: Is this LINQ query in its simplest form?
return activeReport.GetSelectedFieldValues(fieldName)
.Select(value => GenericConverter.ToString(value))
.Where(str => str != null)
.ToList();
}
}
And here is my GenericConverter
class, which allows converting an object to a given simple type, if it can. I introduced the private static IConvertible ConvertTo<T>(object value)
method in order to appease the compiler. As I understand, generic types can be restricted to some interface or a subclass in a hierarchy, but I do not think I can say something like
where
T
in (int
,short
, ...double
,DateTime
,string
), which is what I would ultimately like to do, if I could. One alternative would be to create my ownNullableConvert
class and implement methods such asdecimal?
ToDecimal(object value)`
but I do not like having to type all that stuff in. For instance, having to return null on cast failure would require a try
/catch
or other code inside the body of every method.
/// <summary>
/// This class helps to convert from any object to an IConvertible if possible.
/// This is convinient to use with the SqlHelper, ReportHelper class.
/// This class does not convert everything imaginable, but it does support the "standard" IConvertible.
/// </summary>
public static class GenericConverter
{
public static bool IsNullValue(object value)
{
return (value == null || value == DBNull.Value);
}
public static string ToString(object value)
{
if (IsNullValue(value))
{
return null;
}
return Convert.ToString(value);
}
public static T? To<T>(object value)
where T : struct, IConvertible
{
var result = ConvertTo<T>(value);
if (result == null)
{
return null;
}
return (T)result;
}
// http://msdn.microsoft.com/en-us/library/system.iconvertible.aspx
// Boolean, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime, Char
private static IConvertible ConvertTo<T>(object value, bool nullOnCastException = false)
where T : struct, IConvertible
{
if (IsNullValue(value))
{
return null;
}
try
{
if (typeof(T) == typeof(bool))
{
return Convert.ToBoolean(value);
}
if (typeof(T) == typeof(sbyte))
{
return Convert.ToSByte(value);
}
if (typeof(T) == typeof(byte))
{
return Convert.ToByte(value);
}
if (typeof(T) == typeof(char))
{
return Convert.ToChar(value);
}
if (typeof(T) == typeof(short))
{
return Convert.ToInt16(value);
}
if (typeof(T) == typeof(ushort))
{
return Convert.ToUInt16(value);
}
if (typeof(T) == typeof(int))
{
return Convert.ToInt32(value);
}
if (typeof(T) == typeof(uint))
{
return Convert.ToUInt32(value);
}
if (typeof(T) == typeof(long))
{
return Convert.ToInt64(value);
}
if (typeof(T) == typeof(ulong))
{
return Convert.ToUInt64(value);
}
if (typeof(T) == typeof(float))
{
return Convert.ToSingle(value);
}
if (typeof(T) == typeof(double))
{
return Convert.ToDouble(value);
}
if (typeof(T) == typeof(decimal))
{
return Convert.ToDecimal(value);
}
if (typeof(T) == typeof(DateTime))
{
return Convert.ToDateTime(value);
}
// I never trully intended to work with everything that implements IConvertible.
// I just wanted
string fromTypeName = value.GetType().FullName;
string toTypeName = typeof(T).FullName;
string exceptionMessage = String.Format(
"Unable to convert a value '{0}' of type '{1}' to type '{2}'.",
value,
fromTypeName,
toTypeName);
throw new InvalidCastException(message: exceptionMessage);
}
catch // Perhaps restrict these only to the exception types that pertain to casting only. How do I find the exhaustive list though? C# does not have "checked exceptions" like Java does.
{
if (nullOnCastException)
{
return null;
}
throw;
}
}
}