Home > database >  Can I make IParsable<T> accept nullable?
Can I make IParsable<T> accept nullable?

Time:12-21

I want to make safe, generic extension method for string? parsing:

/// <summary>
/// Returns parsed value if success, otherwise default value
/// </summary>
public static T? ParseTo<T>(this string? value, IFormatProvider? formatProvider = null) where T : IParsable<T>
{
    return T.TryParse(value, formatProvider, out var result) ? result : default;
}

This works like that:

"2022-12-20".ParseTo<DateTime>(); // (DateTime)2022-12-20
"".ParseTo<DateTime>(); // (DateTime)0001-01-01

How can I make it accept nullable types? I.e.:

"2022-12-20".ParseTo<DateTime?>(); // (DateTime?)2022-12-20
"".ParseTo<DateTime?>(); // (DateTime?)null

My intermediary solution is to have additional method:

/// <summary>
/// returns null if value is default or value otherwise
/// </summary>
public static T? NullIfDefault<T>(this T value) where T : struct
{
    return EqualityComparer<T>.Default.Equals(value, default) ? null : value;
}

which works as expected:

[Test]
public async Task ParseCorrectly()
{
    DateTime? date1 = string.Empty.ParseTo<DateTime>();
    DateTime? date2 = string.Empty.ParseTo<DateTime>().NullIfDefault();
    DateTime date3 = string.Empty.ParseTo<DateTime>();
    DateTime date4 = "2022-12-20".ParseTo<DateTime>();
    DateTime? date5 = "2022-12-20".ParseTo<DateTime>().NullIfDefault();
    DateTime date6 = ((string?)null).ParseTo<DateTime>();
    DateTime? date7 = ((string?)null).ParseTo<DateTime>().NullIfDefault();
            
    Assert.Multiple(() =>
    {
        Assert.That(date1, Is.EqualTo((DateTime)default));
        Assert.That(date2, Is.Null);
        Assert.That(date3, Is.EqualTo((DateTime)default));
        Assert.That(date4, Is.EqualTo(DateTime.Parse("2022-12-20")));
        Assert.That(date5, Is.EqualTo(DateTime.Parse("2022-12-20")));
        Assert.That(date6, Is.EqualTo((DateTime)default));
        Assert.That(date7, Is.Null);
    });
}

CodePudding user response:

AFAIK there is no nice solution to this problem due to the fact that generic constraints are not a part of method signature (there are some workarounds - for example see here, but in general it is not considered a good approach). I would suggest instead of adding NullIfDefault method and chaining it - create a specialized method for value types:

public static T? ParseStructToNullable<T>(this string? value, IFormatProvider? formatProvider = null)
    where T : struct, IParsable<T>
    => T.TryParse(value, formatProvider, out var result)
        ? result
        : null;
"2022-12-20".ParseTo<DateTime>(); // (DateTime)2022-12-20
"".ParseTo<DateTime>(); // (DateTime)0001-01-01

var structToNullable = "2022-12-20".ParseStructToNullable<DateTime>(); // (DateTime)2022-12-20
var structTo = "".ParseStructToNullable<DateTime>(); // null
  • Related