【问题标题】:Nullable type as a generic parameter possible?可空类型作为泛型参数可能吗?
【发布时间】:2008-10-16 15:53:42
【问题描述】:

我想做这样的事情:

myYear = record.GetValueOrNull<int?>("myYear"),

注意可空类型作为泛型参数。

由于 GetValueOrNull 函数可以返回 null,我的第一次尝试是这样的:

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : class
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
    {
        return (T)columnValue;
    }
    return null;
}

但我现在得到的错误是:

类型“int?”必须是引用类型才能将其用作泛型类型或方法中的参数“T”

对! Nullable&lt;int&gt;struct!所以我尝试将类约束更改为struct 约束(并且作为副作用不能再返回null):

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct

现在分配:

myYear = record.GetValueOrNull<int?>("myYear");

给出以下错误:

类型“int?”必须是不可为空的值类型才能将其用作泛型类型或方法中的参数“T”

是否尽可能将可空类型指定为泛型参数?

【问题讨论】:

  • 请在DbDataRecord签名IDataRecord..

标签: c# generics


【解决方案1】:

将返回类型改为Nullable&lt;T&gt;,并调用不可为空参数的方法

static void Main(string[] args)
{
    int? i = GetValueOrNull<int>(null, string.Empty);
}


public static Nullable<T> GetValueOrNull<T>(DbDataRecord reader, string columnName) where T : struct
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
        return (T)columnValue;

    return null;
}

【讨论】:

  • 我建议你使用 "columnValue == DBNull.Value" 而不是 'is' 运算符,因为它稍微快一点 =)
  • 个人喜好,但可以使用缩写 T?而不是 Nullable
  • 这对于值类型来说很好,但我认为它根本不适用于引用类型(例如 GetValueOrNull),因为 C# 似乎不喜欢 Nullable 像“字符串?”。如果您需要的话,下面的 Robert C Barth 和 James Jones 的解决方案对我来说似乎要好得多。
  • @bacar - 对,因此是“where T: struct”,如果你想要引用类型,你可以使用“where T: class”创建一个类似的方法
  • @Greg - 当然,但是您需要第二种方法,并且您不能重载名称。正如我所说,如果您想同时处理 val 和 ref 类型,我认为此页面上提供了更简洁的解决方案。
【解决方案2】:
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

    if (!(val is DBNull))
        return (T)val;

    return default(T);
}

像这样使用它:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);

【讨论】:

  • 这可以缩短为:return rdr.IsDBNull(index) ?默认(T) : (T)rdr[index];
  • 我认为这个问题明确想要 null,而不是 default(T)
  • @mafu default(T) 对引用类型返回 null,对数值类型返回 0,使解决方案更加灵活。
  • 我认为将其称为GetValueOrDefault 以澄清它返回default(T) 而不是null 更清楚。或者,如果T 不可为空,您可以让它抛出异常。
  • 这种方法有很多优点,并且迫使您考虑返回除 null 之外的其他内容。
【解决方案3】:

只需对您的原始代码做两件事 - 删除 where 约束,并将最后一个 returnreturn null 更改为 return default(T)。这样你就可以返回任何你想要的类型。

顺便说一句,您可以通过将if 语句更改为if (columnValue != DBNull.Value) 来避免使用is

【讨论】:

  • 这个解决方案不起作用,因为 NULL 和 0 之间存在逻辑差异
  • 如果他传递的类型是int?。它将返回 NULL,就像他想要的那样。如果他将 int 作为类型传递,它将返回 0,因为 int 不能为 NULL。除了我试过而且效果很好。
  • 这是最正确、最灵活的答案。不过return default 就足够了(你不需要(T),编译器会从签名返回类型推断出来)。
【解决方案4】:

免责声明:此答案有效,但仅用于教育目的。 :) James Jones' solution 可能是这里最好的,当然也是我会选择的。

C# 4.0 的 dynamic 关键字使这变得更加容易,即使不太安全:

public static dynamic GetNullableValue(this IDataRecord record, string columnName)
{
  var val = reader[columnName];

  return (val == DBNull.Value ? null : val);
}

现在您不需要 RHS 上的显式类型提示:

int? value = myDataReader.GetNullableValue("MyColumnName");

事实上,你在任何地方都不需要它!

var value = myDataReader.GetNullableValue("MyColumnName");

value 现在将是一个 int、一个字符串或从 DB 返回的任何类型。

唯一的问题是,这不会阻止您在 LHS 上使用不可为空的类型,在这种情况下,您会得到一个相当讨厌的运行时异常,例如:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert null to 'int' because it is a non-nullable value type

与所有使用 dynamic 的代码一样:警告编码器。

【讨论】:

  • 在这种情况下我会避免使用动态类型,因为它们不是必需的。动态类型有很大的性能开销。
【解决方案5】:

我认为您想处理引用类型和结构类型。 我使用它来将 XML Element 字符串转换为更多类型的类型。 您可以使用反射删除 nullAlternative。 formatprovider 用于处理依赖于文化的 '.'或 ',' 分隔符,例如小数或整数和双精度数。 这可能有效:

public T GetValueOrNull<T>(string strElementNameToSearchFor, IFormatProvider provider = null ) 
    {
        IFormatProvider theProvider = provider == null ? Provider : provider;
        XElement elm = GetUniqueXElement(strElementNameToSearchFor);

        if (elm == null)
        {
            object o =  Activator.CreateInstance(typeof(T));
            return (T)o; 
        }
        else
        {
            try
            {
                Type type = typeof(T);
                if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
                {
                    type = Nullable.GetUnderlyingType(type);
                }
                return (T)Convert.ChangeType(elm.Value, type, theProvider); 
            }
            catch (Exception)
            {
                object o = Activator.CreateInstance(typeof(T));
                return (T)o; 
            }
        }
    }

你可以这样使用它:

iRes = helper.GetValueOrNull<int?>("top_overrun_length");
Assert.AreEqual(100, iRes);



decimal? dRes = helper.GetValueOrNull<decimal?>("top_overrun_bend_degrees");
Assert.AreEqual(new Decimal(10.1), dRes);

String strRes = helper.GetValueOrNull<String>("top_overrun_bend_degrees");
Assert.AreEqual("10.1", strRes);

【讨论】:

    【解决方案6】:

    只是不得不做一些与此类似的令人难以置信的事情。我的代码:

    public T IsNull<T>(this object value, T nullAlterative)
    {
        if(value != DBNull.Value)
        {
            Type type = typeof(T);
            if (type.IsGenericType && 
                type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
            {
                type = Nullable.GetUnderlyingType(type);
            }
    
            return (T)(type.IsEnum ? Enum.ToObject(type, Convert.ToInt32(value)) :
                Convert.ChangeType(value, type));
        }
        else 
            return nullAlternative;
    }
    

    【讨论】:

      【解决方案7】:

      多个通用约束不能以 OR 方式(限制较少)组合,只能以 AND 方式(限制较多)组合。这意味着一种方法不能同时处理这两种情况。通用约束也不能用于为方法创建唯一签名,因此您必须使用 2 个单独的方法名称。

      但是,您可以使用通用约束来确保正确使用方法。

      就我而言,我特别希望返回 null,而不是任何可能值类型的默认值。 GetValueOrDefault = 不好。 GetValueOrNull = 好。

      我使用“Null”和“Nullable”这两个词来区分引用类型和值类型。这是我编写的几个扩展方法的示例,它们补充了 System.Linq.Enumerable 类中的 FirstOrDefault 方法。

          public static TSource FirstOrNull<TSource>(this IEnumerable<TSource> source)
              where TSource: class
          {
              if (source == null) return null;
              var result = source.FirstOrDefault();   // Default for a class is null
              return result;
          }
      
          public static TSource? FirstOrNullable<TSource>(this IEnumerable<TSource?> source)
              where TSource : struct
          {
              if (source == null) return null;
              var result = source.FirstOrDefault();   // Default for a nullable is null
              return result;
          }
      

      【讨论】:

        【解决方案8】:

        以防它对某人有所帮助 - 我以前使用过它,并且似乎可以做我需要的事情......

        public static bool HasValueAndIsNotDefault<T>(this T? v)
            where T : struct
        {
            return v.HasValue && !v.Value.Equals(default(T));
        }
        

        【讨论】:

          【解决方案9】:

          这可能是一个死线程,但我倾向于使用以下内容:

          public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
          where T : struct 
          {
              return reader[columnName] as T?;
          }
          

          【讨论】:

          • "类型 'T' 必须是不可为空的值类型才能在泛型类型或方法 'Nullable' 中用作参数 'T'"
          【解决方案10】:

          更短的方式:

          public static T ValueOrDefault<T>(this DataRow reader, string columnName) => 
                  reader.IsNull(columnName) ? default : (T) reader[columnName];
          

          int返回0,为int?返回null

          【讨论】:

            【解决方案11】:

            我自己也遇到了同样的问题。

            ... = reader["myYear"] as int?; 工作正常且干净。

            它适用于任何类型而没有问题。如果结果为 DBNull,则在转换失败时返回 null。

            【讨论】:

            • 事实上,您可以使用int v=reader["myYear"]??-1; 或其他默认值来代替-1。但是,如果值为 DBNull...,这可能会带来问题
            【解决方案12】:

            我知道这是旧的,但这是另一个解决方案:

            public static bool GetValueOrDefault<T>(this SqlDataReader Reader, string ColumnName, out T Result)
            {
                try
                {
                    object ColumnValue = Reader[ColumnName];
            
                    Result = (ColumnValue!=null && ColumnValue != DBNull.Value) ? (T)ColumnValue : default(T);
            
                    return ColumnValue!=null && ColumnValue != DBNull.Value;
                }
                catch
                {
                    // Possibly an invalid cast?
                    return false;
                }
            }
            

            现在,您不必关心 T 是值类型还是引用类型。只有当函数返回 true 时,您才能从数据库中获得合理的值。 用法:

            ...
            decimal Quantity;
            if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
            {
                // Do something with Quantity
            }
            

            这种方法与int.TryParse("123", out MyInt);非常相似

            【讨论】:

            • 如果您按照自己的命名约定工作会很好。他们缺乏一致性。在一个地方有一个没有大写的变量,然后有一个有大写的变量。方法的参数也是如此。
            • 大功告成!希望代码现在看起来更好。鲍勃是你的阿姨 :) 一切都是 skookum
            【解决方案13】:

            这是我多年来一直使用的扩展方法:

            public static T GetValue<T>(this DbDataReader reader, string columnName)
            {
                if (reader == null) throw new ArgumentNullException(nameof(reader));
                if (string.IsNullOrWhiteSpace(columnName))
                    throw new ArgumentException("Value cannot be null or whitespace.", nameof(columnName));
            
                // do not swallow exceptions here - let them bubble up to the calling API to be handled and/or logged
                var index = reader.GetOrdinal(columnName);
                if (!reader.IsDBNull(index))
                {
                    return (T)reader.GetValue(index);
                }
                return default;
            }
            

            【讨论】:

              猜你喜欢
              • 2021-02-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-10-27
              • 1970-01-01
              • 2017-06-25
              相关资源
              最近更新 更多