1
\$\begingroup\$

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 own NullableConvert class and implement methods such as decimal?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;
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ For sql at least look at MicroORMs like Dapper and Massive. I have used Dapper on a multi-month project and was very happy. Not the end-all be-all but really very nice for most of the time. \$\endgroup\$ – George Mauer Mar 20 '12 at 4:44
  • \$\begingroup\$ @George Mauer, do you know one that works specifically with an ODBC source? \$\endgroup\$ – Leonid Mar 20 '12 at 18:22
  • \$\begingroup\$ Here's the entire source code of dapper: github.com/SamSaffron/dapper-dot-net/blob/master/Dapper/… it is about 2200 well documented lines and on a scan I don't see anything that ties it to a particular data source. Even if it does, it's small enough that you should be able to just grab it and make a few modifications to get it working. At the very least you can steal some of their ideas. \$\endgroup\$ – George Mauer Mar 20 '12 at 18:37
2
\$\begingroup\$

I believe you can shorten this by using Convert.ChangeType. Also I altered your catch to handle only InvalidCastException since the name of your bool argument is quite specific. You may want to consider how you'll handle FormatException and OverflowException.

private static IConvertible ConvertTo<T>(object value, bool nullOnCastException = false)
    where T : struct, IConvertible
{
    if (IsNullValue(value))
    {
        return null;
    }

    try
    {
        return (T)Convert.ChangeType(value, typeof(T));
    }
    catch (InvalidCastException)
    {
        if (nullOnCastException)
        {
            return null;
        }

        throw;
    }
}

}

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.