0

I'm trying to code a generic struct to parse nullable types.

I have several problems:

  1. I can't inherit Nullable<T> cause it's a struct:

    public struct NullableParsable<T> : Nullable<T> where T : struct { }
    

Type T in interface list is not an interface

So I try with:

    public struct NullableParsable<T> where T : struct
    {
        public static T? Parse(string s)
        {
            T? result = null;
            string tName = typeof(T).Name;
            switch(tName)
            {
                case "Int32": result = string.IsNullOrEmpty(s) ? null : (int?)int.Parse(s); break;
                case "Decimal": result = string.IsNullOrEmpty(s) ? null : (decimal?)decimal.Parse(s); break;
                default: throw new NotImplementedException("unmanaged type: "+ tName);
            }

            return result;
        }
    }
  1. I can't cast int to T or int? to T?.

  2. I would like a simple way to switch among struct types (maybe an enum), for the moment I switch among type names typeof(T).Name. Or maybe a reflection mecanism to invoke Parse...

See complete code in this fiddle: https://dotnetfiddle.net/x6kHzx

Any idea of how to properly implement this functionality?

sinsedrix
  • 4,336
  • 4
  • 29
  • 53

3 Answers3

1

Since you'll not be able to deal with all structs (outside of hardcoded list, no Parse locatable by reflection), I'd instead recommend some extension methods on string, for those types you know how to support. E.g.

public static class ParseExtensions {
    public static int? ParseInt(this string input)
    {
        if(Int32.TryParse(input,out var result)
        {
           return result;
        }
        return null;
    }

    public static DateTime? ParseDateTime(this string input)
    {
        if(DateTime.TryParse(input, out var result)
        {
            return result;
        }
        return null;
    }
}

You get no benefits from generics here anyway, because all of the Parse/TryParse methods are separate methods that just happen to share a name. They don't have a shared inheritance/interface ancestor.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • This is very much like something I use myself - only the names are different - and the DateTime have a few useful overloads (to handle different formats and stuff like that). +1. – Zohar Peled Aug 02 '18 at 09:36
  • 1
    and why not just directly use TryParse when you can also use `out var` – M.kazem Akhgary Aug 02 '18 at 09:36
  • It works but it's not elegant, I want something generic – sinsedrix Aug 02 '18 at 09:54
  • @sinsedrix - there is **no** point in writing a generic here. As I tried to indicate, all you "add" with a generic here is code that's going to fail at runtime due to the fact that you cannot actually write a generic implementation and you'll be switching on known types and/or relying on reflection. You're taking compile time type safety and turning it into a runtime error. – Damien_The_Unbeliever Aug 02 '18 at 09:56
  • I prefer switching on class names than writing N methods – sinsedrix Aug 02 '18 at 09:59
  • @sinsedrix - we'll have practically the same number of *lines of code*, and my code doesn't lie about its abilities, maintaining type safety. – Damien_The_Unbeliever Aug 02 '18 at 10:01
1

Apart from advice already given, what you can do is use a dictionary instead of a switch statement to make it a bit more dynamic. This doesn't change that only specified types can be parsed.

For example, you can create a class that knows how to parse these types:

public class NullableParsable
{
    private static readonly Dictionary<string, Func<string, object>> _parsers =
        new Dictionary<string, Func<string, object>>();

    public static void Register<T>(Func<string, T?> parser)
        where T : struct
    {
        var key = typeof(T).FullName;
        _parsers.Add(key, x => parser(x));
    }

    public static T? Parse<T>(string value)
        where T : struct
    {
        var key = typeof(T).FullName;
        if (_parsers.TryGetValue(key, out var parser))
        {
            if (string.IsNullOrEmpty(value))
            {
                return null;
            }

            return (T?) parser(value);
        }

        throw new NotSupportedException("Not sure how to map this type");
    }
}

And after that, specify how to parse specific types:

NullableParsable.Register<int>(s => int.Parse(s));
NullableParsable.Register<decimal>(s => decimal.Parse(s));
NullableParsable.Register<Guid>(s => Guid.Parse(s));

Example usage:

int? result1 = NullableParsable.Parse<int>("123");
decimal? result2 = NullableParsable.Parse<decimal>("123");
Guid? result3 = NullableParsable.Parse<Guid>(Guid.NewGuid().ToString("D"));
Caramiriel
  • 7,029
  • 3
  • 30
  • 50
-1

In fact, I just have to declare my result as a dynamic then I can assign it a Nullable and return it as a T?:

public static T? Parse(string s)
{
    dynamic result = null;
    int intValue;
    result = int.TryParse(s, out intValue) ? (int?)intValue : null;
    return result;
}

And with relfection, I can invoke Parse:

public static T? Parse(string s)
{
    dynamic result = null;
    Type type = typeof(T);
    string tName = type.Name;
    MethodInfo methodInfo = type.GetMethod("Parse", new[] { typeof(string) });
    result = methodInfo.Invoke(null, new[] { s });
    return result;
}

Better, I can invoke TryParse to avoid try/catch:

public static T? Parse(string s)
{
    Type type = typeof(T);
    string tName = type.Name;
    MethodInfo methodInfo = type.GetMethod("TryParse", new[] { typeof(string), type.MakeByRefType() });
    object[] args = new object[] { s, null };
    bool parsed = (bool)methodInfo.Invoke(null, args);
    dynamic result = parsed ? args[1] : null;
    return result;
}

Updated fiddle

Related posts: How to call TryParse dynamically?, Generic TryParse,

Community
  • 1
  • 1
sinsedrix
  • 4,336
  • 4
  • 29
  • 53