0

I have a database with a nullable column foo_date, where Npgsql maps the sql NULL value to an instance of the C# class DBNull. In my C# aggregate I use the type DateTime? for said column. So the question is how to easily convert DBNull to a nullable type.

I want to write a utility method like, e.g.,

public static class DbUtil
{
    public static T? CastToNullable<T>(object obj)
    {
        if (DBNull.Value.Equals(obj))
           return null;
        return (T)obj;
    }
}

which I would like to use like this:

IDataRecord rec = ...
DateTime? fooDate = DbUtil.CastToNullable<DateTime>(rec["foo_date"]);

However, I get the compiler error:

Error CS0403 Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.

When I replace return null by return default(T?), the compiler is happy, but the method does not return null but the default Date, i.e., 01.01.0001.

What is the correct way to write the generic utility method above?

Filburt
  • 17,626
  • 12
  • 64
  • 115
Joerg
  • 790
  • 2
  • 10
  • 23
  • 2
    Does this answer your question? [How can I return NULL from a generic method in C#?](https://stackoverflow.com/questions/302096/how-can-i-return-null-from-a-generic-method-in-c) – Joelius May 27 '21 at 12:42
  • No, it doesn't. However, I found this: [Nullable type as a generic parameter possible?](https://stackoverflow.com/questions/209160/nullable-type-as-a-generic-parameter-possible) which is actually closer to my question, but produces a different compiler error: "CS0266 Cannot implicitly convert type 'T' to 'T?'. An explicit conversion exists (are you missing a cast?)". – Joerg May 27 '21 at 12:47
  • but `default(DateTime?)` is null .. so change to `null` to `default(T)` replace `T?` with `T` and use like `DbUtil.CastToNullable(...)` – Selvin May 27 '21 at 12:47
  • 1
    You probably need `where T : struct`. Without that, `T?` just means "If `T` is a nullable reference type, then allow it to be null; otherwise, no effect". With the `struct` constraint, `T?` means `Nullable`, which is what you want – canton7 May 27 '21 at 12:47
  • canton7's Comment worked. Thanks! If you add this as an answer, I will accept it. Does anybody know why I get said compiler error in [Nullable type as a generic parameter possible?](https://stackoverflow.com/questions/209160/nullable-type-as-a-generic-parameter-possible), while other people seem to be fine with the accepted answer? – Joerg May 27 '21 at 12:51
  • 2
    But... that's exactly what @Joelius' link says to do... – Heretic Monkey May 27 '21 at 12:53
  • But the accepted answer from @Joelius' link describes something else and focuses on the reference case. Also, the type signature of the question's code example is different. Sorry if I didn't read in between the lines enough. – Joerg May 27 '21 at 12:58

3 Answers3

2

Please check Nullable structure which is in fact long/real version of T?

In that case your method would be

public static T? CastToNullable<T>(object obj) where T: struct
{
    if (DBNull.Value.Equals(obj))
       return null;
    return new Nullable<T>((T)obj);
}

That would work only for structures, for reference types you could add

public static T CastToNullableObj<T>(object obj) where T: class
{
    if (DBNull.Value.Equals(obj))
       return null;
    return (T)obj;
}
ASpirin
  • 3,601
  • 1
  • 23
  • 32
2

I'm assuming you've got nullable reference types enabled, otherwise that T? would be a compiler error.

T?, where T is a generic type parameter, has multiple meanings unfortunately.

  • When T is a value type (i.e. you have a where T : struct constraint), T? means Nullable<T>.
  • When T is a reference type (i.e. you have a where T : class constraint), T? means a reference type which is allowed to be null.
  • When T is unconstrained, T? means "If T is a reference type, then this is allowed to be null; otherwise no effect".

In other words, if you have:

T? Foo<T>() => default;

If you call Foo<int>(), you get back an int, not an int?.

If however you have:

T? Foo<T>() where T : struct => default;

then Foo<int>() returns an int?.

In other words, your signature needs to be:

public static T? CastToNullable<T>(object obj) where T : struct
{
    if (DBNull.Value.Equals(obj))
       return null;
    return (T)obj;
}
canton7
  • 37,633
  • 3
  • 64
  • 77
1

It seems like you want to access columns on your NpgsqlDataReader, but to get .NET null for null columns instead of DBNull.Value. If so, the usual way is to write use an extension method as follows:

public static class DbDataReaderExtensions
{
    public static T? GetValueOrDefault<T>(this DbDataReader reader, int ordinal)
        where T : class
        => reader.IsDBNull(ordinal) ? null : reader.GetFieldValue<T>(ordinal);
}

This can be used directly on your reader:

var s = reader.GetValueOrDefault<string>(0);
Shay Rojansky
  • 15,357
  • 2
  • 40
  • 69