1

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);
    });
}
Pawel
  • 891
  • 1
  • 9
  • 31
  • 1
    try `where T : struct, IParsable` – Vivek Nuna Dec 20 '22 at 10:48
  • I don't think there's a good way. Even though `DateTime` implements `IParsable`, that doesn't mean that the `Nullable` struct implements `IParsable>`. You probably need something like `public static T? TryParseTo(...)` – canton7 Dec 20 '22 at 10:54
  • Use the try pattern to implement this. I am a co-author of a functional c# library, that uses this pattern with a source generator to provide all these functions (with our own option type): Usage: https://polyadic.github.io/funcky/try-pattern.html Generated code example: https://github.com/polyadic/funcky/commit/391f99b9eb973aee66f7e85cc456c8473ce23439 If you want - go ahead and steal this (including the generator), it's open source! – Mafii Dec 20 '22 at 11:09
  • Thanks for the suggestions. I added second method that works as expected. I'm wondering if they can be merged into one, but if not I'm happy chaining them. – Pawel Dec 20 '22 at 11:43

1 Answers1

1

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
Guru Stron
  • 102,774
  • 10
  • 95
  • 132