Nullable类型作为通用参数可能吗?

Tom*_*ter 268 c# generics

我想做这样的事情:

myYear = record.GetValueOrNull<int?>("myYear"),
Run Code Online (Sandbox Code Playgroud)

请注意可空类型作为通用参数.

由于该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;
}
Run Code Online (Sandbox Code Playgroud)

但我现在得到的错误是:

类型'int?' 必须是引用类型才能在泛型类型或方法中将其用作参数"T"

对!Nullable<int>是一个struct!所以我尝试将类约束更改为struct约束(并且副作用不能再返回null):

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct
Run Code Online (Sandbox Code Playgroud)

现在任务:

myYear = record.GetValueOrNull<int?>("myYear");
Run Code Online (Sandbox Code Playgroud)

给出以下错误:

类型'int?' 必须是非可空值类型才能在泛型类型或方法中将其用作参数"T"

是否可以将可空类型指定为通用参数?

Gre*_*ean 250

将返回类型更改为Nullable,并使用非可空参数调用该方法

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 个人喜好,但你可以使用简短形式T?而不是Nullable <T> (37认同)
  • 这对于值类型来说很好,但是我认为它根本不适用于引用类型(例如GetValueOrNull <string>),因为C#似乎不像Nullable <(ref type)>喜欢"string?".罗伯特C巴特和詹姆斯琼斯的解决方案,如果这是你的需要,对我来说似乎要好得多. (11认同)
  • @Greg - 当然,但是你需要第二种方法,你不能重载名称.正如我所说,*如果*你想同时处理val和ref类型,我认为这个页面上提供了更清晰的解决方案. (4认同)
  • 我建议你使用 "columnValue == DBNull.Value" 而不是 'is' 运算符,因为它稍微快一些 =) (2认同)
  • @bacar - 右边,因此"where T:struct",如果你想要引用类型,你可以用"where T:class"创建一个类似的方法 (2认同)

Jam*_*nes 104

public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

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

    return default(T);
}
Run Code Online (Sandbox Code Playgroud)

只需像这样使用它:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);
Run Code Online (Sandbox Code Playgroud)

  • 我认为这个问题显然需要_null_,而不是_default(T)_. (11认同)
  • 这可以缩短为:return rdr.IsDBNull(index)?default(T):( T)rdr [index]; (5认同)
  • @mafu default(T)将为引用类型返回null,为数值类型返回0,使解决方案更加灵活. (4认同)
  • 我认为要么调用这个`GetValueOrDefault`以澄清它返回`default(T)`而不是`null`更清楚.或者,如果`T`不可为空,你可以抛出异常. (2认同)

Rob*_*rth 59

只需要做两件事你原来的代码-去掉where约束,最后改变returnreturn nullreturn default(T).这样你就可以返回你想要的任何类型.

顺便说一下,你可以is通过改变你的if陈述来避免使用if (columnValue != DBNull.Value).

  • 如果他传递的类型是int?它可以工作.它将返回NULL,就像他想要的那样.如果他将int作为类型传递,它将返回0,因为int不能为NULL.除了我尝试它的事实,它的工作完美. (15认同)
  • 此解决方案不起作用,因为NULL和0之间存在逻辑差异 (4认同)
  • 这是最正确、最灵活的答案。然而,`return default` 就足够了(你不需要 `(T)`,编译器会从签名返回类型推断它)。 (2认同)

Cas*_*mer 8

多个通用约束不能以 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;
    }
Run Code Online (Sandbox Code Playgroud)


小智 5

我认为您想处理引用类型和结构类型。我使用它来将 XML 元素字符串转换为更类型化的类型。您可以使用反射删除 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; 
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

你可以这样使用它:

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);
Run Code Online (Sandbox Code Playgroud)


Ian*_*emp 5

免责声明:此答案有效,但仅用于教育目的。:)詹姆斯·琼斯(James Jones)的解决方案可能是这里最好的解决方案,当然也是我会选择的解决方案。

C#4.0的dynamic关键字使此操作更容易,即使不太安全:

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

  return (val == DBNull.Value ? null : val);
}
Run Code Online (Sandbox Code Playgroud)

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

int? value = myDataReader.GetNullableValue("MyColumnName");
Run Code Online (Sandbox Code Playgroud)

实际上,您甚至根本不需要它!

var value = myDataReader.GetNullableValue("MyColumnName");
Run Code Online (Sandbox Code Playgroud)

value 现在将是一个int或一个字符串,或从数据库返回的任何类型。

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

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert null to 'int' because it is a non-nullable value type
Run Code Online (Sandbox Code Playgroud)

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

  • 在这种情况下我会避免使用动态类型,因为它们不是必需的。动态类型具有显着的性能开销。 (3认同)