Cannot change type to nullable in generic method
Asked Answered
D

3

5

I am creating a generic converter

Here is a sample code of the generic converter

bool TryReaderParse<TType>(object data, out TType value)
{
    value = default(TType);
    Type returnType = typeof(TType);
    object tmpValue = null;

    if (returnType == typeof(DateTime))
    {
        tmpValue = StringToDatetime(data.ToString());
    }
    else if (returnType == typeof(DateTime?)) // THIS IF FIRES
    {
        tmpValue = StringToNullableDatetime(data.ToString());
    }

    value = (TType)Convert.ChangeType(tmpValue, returnType);  // THROWS
}

public DateTime? StringToNullableDatetime(string date)
{
    DateTime? datetime = null;
    if (!string.IsNullOrEmpty(date))
    {
        datetime = DateTime.Parse(date, new CultureInfo(Resources.CurrentCulture));
    }

    return datetime;
}

And this is how I use it:

void foo()
{
    DateTime? date = null;
    TryReaderParse<DateTime?>("25/12/2012", out date);
}

The thrown exception says that it cannot convert from DateTime to Nullable<DateTime>. Since, the method creates and returns a nullable type, how come the casting fails?

At the end, I want to have a nullable DateTime, in this particular example.

edit The problem is that StringToNullableDatetime method returns a Datetime? and the casting says that cannot convert from Datetime

Since the StringToNullableDatetime method returns a nullable datetime, how is it possible that the Convert.ChangeType cannot see that the passed argument is nullable?

Ps. I've read answers like this one that do the opposite (casting from nullable).

Dobruja answered 25/4, 2012 at 15:1 Comment(0)
M
18

The thrown exception says that it cannot convert from DateTime to Nullable<DateTime>. Since, the method creates and returns a nullable type, how come the casting fails?

Good question. This fails because there is no such thing as a boxed nullable. When you convert a DateTime? to object, you either get a null reference, if the DateTime? was null, or you get the boxed value, a DateTime. You never get a boxed nullable struct; there is no such thing.

Therefore you end up with either null, or a valid DateTime in that box. You then tell Convert to convert that to a nullable DateTime, and Convert does not know how to do that.

My advice is that you abandon this line of attack entirely; this code is borderline abusive of generics. Any time you make a switch on the specific type of a generic, your code is no longer generic and you are probably doing it wrong. If you want to do a "try"-style method for datetimes then just write that:

DateTime? TryReadDateTime(object data)
{
    ... return null if the object cannot be read as a datetime ...
}

Write such a method for every type that you intend to read. The user would much rather write:

DateTime? d = TryReadDateTime(data);
if (d != null) ...

Than

DateTime d;
bool b = TryRead<DateTime>(data, out d);
if (b) ...
Marginal answered 25/4, 2012 at 15:23 Comment(0)
T
0

From the documentation, this line will error if:

value is null and conversionType is a value type

Nullable<T> is a struct and hence a value type, thus you can't use this method call if your value is null. You already handle dates separately, so why use ChangeType in those cases anyway?

Tropology answered 25/4, 2012 at 15:5 Comment(1)
edited my question. My problem is that I cannot return a Nullable datetime. The Convert.ChangeType line cannot see that the passed argument is nullableDobruja
S
0

The way nullables, generics, and boxing interact is weird. You may be better off defining two methods:

bool TryReaderParse(object data, out TType value);
bool TryReaderParse(object data, out TType? value) where TType : struct;

Within the second method, your code can simply produce a TType and assign it to the TType? without difficulty.

Scram answered 25/4, 2012 at 15:20 Comment(1)
First off, a method may not be overloaded on constraints alone. Second, if the method returns a nullable value then why does it also need to return a bool? If you're going to do this then the right signatures are T TryParseClass<T>(object data) where T : class and T? TryParseStruct<T>(object data) where T : struct. No bool necessary.Marginal

© 2022 - 2024 — McMap. All rights reserved.