May
May

Reputation: 100

Custom default value for generic parameter

I'm trying to write a generic method to read data from SQLDataReader. It works really well except when I want to get custom default value for some data types. For example, for string I want to get string.Empty instead of null.

public static T SafeGetValue<T>(SqlDataReader dr, string columnName)
{
    T returnValue = default(T);
    var value = dr[columnName];

    if (value != null && value != DBNull.Value)
    {
        returnValue = (T)value;
    }
    else
    {
        returnValue.Null();
    }

    return returnValue;
}

public static object Null(this object o)
{
    return null;
}

public static string Null(this string stringValue)
{
    return string.Empty;
}

When T is string, I'm trying to get it to goto Null overload of string but it still goes to the object overload. Is there any way to do this?

Upvotes: 2

Views: 1469

Answers (4)

mykhailo.romaniuk
mykhailo.romaniuk

Reputation: 1088

For such case I'm using optional parameter in method with default value. So you override default behavior only where it's needed:

public static T GetValueOrDefault<T>(SqlDataReader dr, string columnName, T defaultValue = default(T))
{
     T returnValue = default(T);
     var value = dr[columnName];

     return value == null ? (T) defaultValue : value;
}

If you want to go even deeper and handle cases when column doesn't exists or you have deals with Nullable types - here the part of code I'm using in my projects:

public static T GetValueOrDefault<T>(IDataRecord row, string fieldName, T defaultValue = default(T))
{
    if (HasColumn(row, fieldName))
    {
        int ordinal = row.GetOrdinal(fieldName);
        object value = row.GetValue(ordinal);

        return row.IsDBNull(ordinal)
            ? defaultValue
            : ConvertValue<T>(value);
    }
    else
    {
        return defaultValue;
    }
}

public static bool HasColumn(IDataRecord row, string columnName)
{
    for (var i = 0; i < row.FieldCount; i++)
    {
        if (row.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
        {
            return true;
        }
    }
    return false;
}

public static T ConvertValue<T>(object value)
{
    var type = typeof(T);
    type = Nullable.GetUnderlyingType(type) ?? type;

    var result = Convert.ChangeType(value, type);
    return (T)result;
}

Upvotes: 0

DavidG
DavidG

Reputation: 119156

To expand on the comment I gave above, I suggested passing in a default value, but you can also pass in a generator instead, something you would only ever need to change in a single place. For example:

public class DefaultValueGenerator<T>
{
    public virtual T Default()
    {
        return default(T);
    }
}

public class StringValueGenerator : DefaultValueGenerator<string>
{
    public override string Default()
    {
        return "";
    }
}

public static T SafeGetValue<T>(SqlDataReader dr, string columnName, 
    DefaultValueGenerator<T> defaultGenerator)
{
    //snip
    returnValue = defaultGenerator.Default();
}

And use it like this:

var stringDefaultGenerator = new StringValueGenerator();
var x = SafeGetValue<string>(dr, "column", sg);

Upvotes: 1

Zdeněk Jel&#237;nek
Zdeněk Jel&#237;nek

Reputation: 2923

This smells like you're trying to have your data access layer cover up for data-not-present errorhandling in upper layers.

While that may seem like a good idea to avoid some errors globally, it might hurt you in the long run as you might not be able to distinguish between "record not present" and "record has the value I chose to cover up null" in the future. It also won't help you in cases where you'd like to supply a different default for the same type in a different usage scenario.

I just wanted to put this up so that you have a clear idea that there is also a price for taking this path.

Personally, I'd go for a T defaultValue parameter, have some Option<T> as a return type or possibly an out T parameter and a bool return type. This gives up neither compile-time safety, nor per-usage missing record handling.

Upvotes: 0

Alberto Chiesa
Alberto Chiesa

Reputation: 7360

The Null method is statically bound to the object version. I think your simplest option is to use a switch, or a dictionary, to handle your special cases. Like this:

private static readonly Dictionary<Type, Object> _NullValues = new Dictionary<Type, Object>()
{
    { typeof(String), String.Empty }
};

public static object Null<T>(this T o)
{
    object ret;
    return _NullValues.TryGetValue(typeof(T), out ret)
        ? ret : default(T);
}

Upvotes: 4

Related Questions