通用参数的自定义默认值

时间:2017-03-16 21:16:46

标签: c# generics default-value

我正在尝试编写一个通用方法来从SQLDataReader读取数据。除非我想获得某些数据类型的自定义默认值,否则它的效果非常好。例如,对于string,我想获得string.Empty而不是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;
}

Tstring时,我正试图让它转到Null的{​​{1}}重载,但它仍会转到string重载。有没有办法做到这一点?

4 个答案:

答案 0 :(得分:4)

Null方法静态绑定到对象版本。我认为你最简单的选择是使用开关或字典来处理你的特殊情况。像这样:

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);
}

答案 1 :(得分:1)

为了扩展我上面给出的评论,我建议传递一个默认值,但你也可以传入一个生成器,这只是你需要在一个地方改变的东西。例如:

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();
}

并像这样使用它:

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

答案 2 :(得分:0)

对于这种情况,我在方法中使用带有默认值的可选参数。因此,只有在需要时才覆盖默认行为:

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;
}

如果你想更深入地处理当列不存在或者你处理 Nullable 类型的情况时 - 这里是我在我的项目中使用的代码部分:

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;
}

答案 3 :(得分:0)

这有点像你试图让你的数据访问层掩盖在上层的数据不存在错误处理。

虽然在全球范围内避免一些错误似乎是一个好主意,但从长远来看可能会对您造成伤害,因为您可能无法区分&#34;记录不存在&#34;和&#34;记录具有我选择掩盖空值的值#34;在将来。如果您希望在不同的使用场景中为同一类型提供不同的默认值,它也会帮助您。

我只是想把它放在一边,这样你就可以清楚地知道走这条道路还有代价。

就个人而言,我选择T defaultValue参数,将某些Option<T>作为返回类型,或者可能是out T参数和bool返回类型。这既没有放弃编译时的安全性,也没有放弃每次使用丢失的记录处理。