通用参数的自定义默认值

Custom default value for generic parameter

我正在尝试编写一个通用方法来从 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 时,我试图让它转到 stringNull 重载,但它仍然转到 object超载。有什么办法吗?

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

对于这种情况,我在具有默认值的方法中使用可选参数。所以你只在需要的地方覆盖默认行为:

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

这听起来像是您试图让数据访问层掩盖上层中的数据不存在错误处理。

虽然这似乎是避免全局错误的好主意,但从长远来看它可能会伤害您 运行 因为您可能无法区分 "record not present" 和 "record has the value I chose to cover up null" 在将来。如果您想在不同的使用场景中为同一类型提供不同的默认值,它也无济于事。

我只是想把这个放上去,让你清楚地知道走这条路也是有代价的。

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

为了扩展我上面给出的评论,我建议传入一个默认值,但您也可以传入一个生成器,您只需要在一个地方进行更改。例如:

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