问题 Generic方法可以处理Reference和Nullable Value类型吗?


我有一系列的Extension方法来帮助对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)。我该如何实现这种行为?


1352
2017-11-19 20:40


起源



答案:


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

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

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


10
2017-11-19 20:51



这是非常糟糕的解决方案。如果在datarecord NULL值中,则不需要默认值int。 NULL和ZERO是不同的值。 - TcKs
如果数据库中的值可以为空,那么T将是Nullable <int>,在这种情况下,NULL是将返回的默认值。 - BFree
如果是这样,为什么这个扩展方法,如果你可以简单地使用“dr [ordinal] as int?” ? - TcKs
如果它是DBNull,dr [ordinal]将抛出异常。 - Adam Lassek
@juan这似乎是一个非常多余的错误。我在这一点上不同意FxCop--明确说明泛型方法的类型并不是坏事。 - Adam Lassek


答案:


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

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

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


10
2017-11-19 20:51



这是非常糟糕的解决方案。如果在datarecord NULL值中,则不需要默认值int。 NULL和ZERO是不同的值。 - TcKs
如果数据库中的值可以为空,那么T将是Nullable <int>,在这种情况下,NULL是将返回的默认值。 - BFree
如果是这样,为什么这个扩展方法,如果你可以简单地使用“dr [ordinal] as int?” ? - TcKs
如果它是DBNull,dr [ordinal]将抛出异常。 - Adam Lassek
@juan这似乎是一个非常多余的错误。我在这一点上不同意FxCop--明确说明泛型方法的类型并不是坏事。 - Adam Lassek


这有效:

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
2017-11-19 21:14



这与BFree的答案几乎相同。样本用法很有帮助。 - Amy B


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

您应该能够通过不使用可空数据类型并返回实际值或返回null来完成相同的操作 建议 由BFree。


1
2017-11-19 20:56



实际上,您可以使用两种重载方法,这些方法仅在返回类型方面有所不同。这是合法的语法:public string Method(){return“”; public int Method(){return 0; } - BFree
@BFree:类型'xyz'已经定义了一个名为'Method'的成员,它具有相同的参数类型。 - Amy B
@BFree:我刚试过你的确切代码并得到以下错误:类型'Program1'已经定义了一个名为'Method'的成员,它具有相同的参数类型。 - Scott Dorman
参数类型相同,但返回类型不同,这是合法的语法。试试看.... - BFree
好的,我有所纠正。出于某种原因,我确信你能做到这一点。马坏...... - BFree


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

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

1
2018-05-14 10:04



正如我已在评论中向BFree解释的那样,如果它是DBNull,dr [ordinal]会抛出异常。你的例子不起作用。 - Adam Lassek
@Adam Lassek - 我不知道为什么你认为我的代码不会工作,你试过吗?我让它在生产系统上运行,请在向下投票之前检查代码。我想大多数新手可以告诉你 dr[ordinal] 如果值为DBNull,则不会抛出异常。 - stevehipwell
什么框架版本?当我写这个问题时,我使用的是3.5,如果该字段是DBNull,它肯定会引发异常。 - Adam Lassek
stackoverflow.com/questions/1855556/datareader-best-practices/... - Adam Lassek
@Adam Lassek - 我已经在.NET 2.0和.NET 3.5上进行了测试(反复)并且它完美无缺。您不会从DataReader将DBNull读取到兼容的对象类型或使用 as 要转换为类型的关键字或设置为null。我建议你制作一个虚拟应用程序并运行上面的方法来证明这一点。你也可以使用 object oObject = oReader[0]; 证明您可以直接从阅读器访问DBNull对象。 - stevehipwell


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

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

0
2017-11-19 23:20





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

0
2018-06-26 00:00



您已经设法实现了通用功能,但没有从中获得最轻微的好处。不必为每种类型编写单独的函数 整点 泛型,以及发布此问题的原因。 - Adam Lassek
也许,但与您的解决方案的不同之处在于,您只有一种方法可以检查是否为空,并且如果您拥有非常大的数据集,则可能需要进行强制转换。 - SeeR
与BFree解决方案相比,你可以避免拳击 - SeeR


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


-1
2017-11-19 20:47



这就是为什么我不能让这个返回Nullable <T>,这就是我问这个问题的原因。 - Adam Lassek


我是这样做的:

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

-2
2017-11-19 20:46



我可以,但这个泛型练习的重点是避免为每种数据类型编写单独的方法。而且我没有看到你的例子是如何相关的...我正在将IDataRecord扩展为null检查数据 从 数据存储区。 - Adam Lassek
如果您想要记录中的检查值,可以使用“as”关键字,它可以执行您想要的操作。如果在记录DbNull中,则返回null。否则,将返回“int”作为“Nullable <int>”。无需特殊方法。 - TcKs
你应该 不 使用扩展方法扩展对象,因为它不能用于所有语言,如VB.NET - Scott Dorman
@Scott Dorman:是的,但前提是你需要符合CLS。 .NET有很多语言,关心它们都是 恕我直言 不好的方式。 - TcKs