Generic方法可以同时处理Reference和Nullable Value类型吗?

时间:2008-11-19 20:40:29

标签: c# .net generics

我有一系列的扩展方法来帮助对IDataRecord对象进行空值检查,我目前正在实现这样:

public static int? GetNullableInt32(this IDataRecord dr, int ordinal)
{
    int? nullInt = null;
    return dr.IsDBNull(ordinal) ? nullInt : dr.GetInt32(ordinal);
}

public static int? GetNullableInt32(this IDataRecord dr, string fieldname)
{
    int ordinal = dr.GetOrdinal(fieldname);
    return dr.GetNullableInt32(ordinal);
}

等等,对于我需要处理的每种类型。

我想重新实现这些作为通用方法,部分是为了减少冗余,部分是为了学习如何编写通用方法。

我写过:

public static Nullable<T> GetNullable<T>(this IDataRecord dr, int ordinal)
{
    Nullable<T> nullValue = null;
    return dr.IsDBNull(ordinal) ? nullValue : (Nullable<T>) dr.GetValue(ordinal);
}

只要T是值类型就可以工作,但如果T是引用类型,则不会。

如果T是值类型,则此方法需要返回Nullable类型,否则返回default(T)。我该如何实现这种行为?

8 个答案:

答案 0 :(得分:10)

您可以像这样声明您的方法:

public static T GetNullable<T>(this IDataRecord dr, int ordinal)
{
    return dr.IsDBNull(ordinal) ? default(T) : (T) dr.GetValue(ordinal);
}

这样,如果T是可以为null的int或任何其他可以为null的值类型,它实际上将返回null。如果它是常规数据类型,它将只返回该类型的默认值。

答案 1 :(得分:2)

这有效:

public static T Get<T>( this IDataRecord dr, int ordinal) 
{
    T  nullValue = default(T);
    return dr.IsDBNull(ordinal) ? nullValue : (T) dr.GetValue(ordinal);
}


public void Code(params string[] args)
{
    IDataRecord dr= null;
    int? a = Get<int?>(dr, 1);
    string b = Get<string>(dr, 2);
}

答案 2 :(得分:1)

我认为你不能用一个函数来实现它。如果C#支持基于返回类型的重载,您可能会这样做,但即便如此,我也建议不要这样做。

你应该能够通过不使用可空数据类型来完成同样的事情,并返回实际值或null,如BFree的suggested

答案 3 :(得分:1)

我无法理解为什么需要使整个过程复杂化。为什么不保持简洁,并使用以下代码行:

对于null有效的值类型,请使用int? iNullable = dr[ordinal] as int?;

对于null无效的值类型,请使用int iNonNullable = dr[ordinal] as int? ?? default(int);

对于参考类型,请使用string sValue = dr[ordinal] as string;

对于那些认为代码不起作用并且dr[ordinal]将为DBNull抛出异常的人来说,这是一个示例方法,一旦给出有效的连接字符串就会证明这个概念。

private void Test()
{
  int? iTestA;
  int? iTestB;
  int iTestC;
  string sTestA;
  string sTestB;

  //Create connection
  using (SqlConnection oConnection = new SqlConnection(@""))
  {
    //Open connection
    oConnection.Open();

    //Create command
    using (SqlCommand oCommand = oConnection.CreateCommand())
    {
      //Set command text
      oCommand.CommandText = "SELECT null, 1, null, null, '1'";

      //Create reader
      using (SqlDataReader oReader = oCommand.ExecuteReader())
      {
        //Read the data
        oReader.Read();

        //Set the values
        iTestA = oReader[0] as int?;
        iTestB = oReader[1] as int?;
        iTestC = oReader[2] as int? ?? -1;
        sTestA = oReader[3] as string;
        sTestB = oReader[4] as string;
      }
    }
  }
}

答案 4 :(得分:0)

你不能用一种方法来做,但你用三种方法做到了:

public static T GetData<T>(this IDataReader reader, Func<int, T> getFunc, int index)
{
    if (!reader.IsClosed)
    {
        return getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

public static T GetDataNullableRef<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : class
{
    if (!reader.IsClosed)
    {
        return reader.IsDBNull(index) ? null : getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

public static T? GetDataNullableValue<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : struct
{
    if (!reader.IsClosed)
    {
        return reader.IsDBNull(index) ? (T?)null : getFunc(index);
    }
    throw new ArgumentException("Reader is closed.", "reader");
}

然后使用它你会做:

private static Whatever CreateObject(IDataReader reader)
{
    Int32? id = reader.GetDataNullableValue<Int32>(reader.GetInt32, 0);
    string name = reader.GetDataNullableRef<string>(reader.GetString, 1);
    Int32 x = reader.GetData<Int32>(reader.GetInt32, 2);
}

答案 5 :(得分:0)

public static T Get<T>(this IDataRecord rec, Func<int, T> GetValue, int ordinal)
{
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(ordinal);
}

或更高效的

public static T Get<T>(this IDataRecord rec, Func<IDataRecord, int, T> GetValue, int ordinal)
{
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(rec, ordinal);
}

public static Func<IDataRecord, int, int> GetInt32 = (rec, i) => rec.GetInt32(i);
public static Func<IDataRecord, int, bool> GetBool = (rec, i) => rec.GetBoolean(i);
public static Func<IDataRecord, int, string> GetString = (rec, i) => rec.GetString(i);

并像这样使用

rec.Get(GetString, index);
rec.Get(GetInt32, index);

答案 6 :(得分:-1)

Nullable结构仅适用于Value类型,因为引用类型无论如何都是可以为空的......

答案 7 :(得分:-2)

我是这样做的:

DataRow record = GetSomeRecord();
int? someNumber = record[15] as int?
Guid? someUID = record["MyPrimaryKey"] as Guid?;
string someText = GetSomeText();
record["Description"] = someText.ToDbString();

// ........

public static class StringExtensionHelper {
    public static object ToDbString( this string text ) {
         object ret = null != text ? text : DBNull.Value
         return ret;
    }
}

修改 您可以(或者您应该)为其他原始类型的课程提供“ToDbInt32,ToDbBool等...”扩展方法。

编辑2: 您还可以使用“ToDbValue”扩展基类“object”。

public static class StringExtensionHelper {
    public static object ToDbValue( this object value ) {
         object ret = object.ReferenceEquals( value, null ) ? (object)DBNull.Value : value;
         return ret;
    }
}