Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot change type to nullable in generic method

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

like image 767
Odys Avatar asked Apr 25 '12 15:04

Odys


1 Answers

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) ...
like image 99
Eric Lippert Avatar answered Oct 04 '22 19:10

Eric Lippert