-->

Generic TryParse

2019-01-02 20:06发布

问题:

I am trying to create a generic extension that uses 'TryParse' to check if a string is a given type:

public static bool Is<T>(this string input)
{
    T notUsed;
    return T.TryParse(input, out notUsed);
}

this won't compile as it cannot resolve symbol 'TryParse'

As I understand, 'TryParse' is not part of any interface.

Is this possible to do at all?

Update:

Using the answers below I have come up with:

public static bool Is<T>(this string input)
{
    try
    {
        TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
    }
    catch
    {
        return false;
    }

    return true;
}

It works quite well but I think using exceptions in that way doesn't feel right to me.

Update2:

Modified to pass type rather than use generics:

public static bool Is(this string input, Type targetType)
{
    try
    {
        TypeDescriptor.GetConverter(targetType).ConvertFromString(input);
        return true;
    }
    catch
    {
        return false;
    }
}

回答1:

You should use the TypeDescriptor class:

public static T Convert<T>(this string input)
{
    try
    {
        var converter = TypeDescriptor.GetConverter(typeof(T));
        if(converter != null)
        {
            // Cast ConvertFromString(string text) : object to (T)
            return (T)converter.ConvertFromString(input);
        }
        return default(T);
    }
    catch (NotSupportedException)
    {
        return default(T);
    }
}


回答2:

I also required a generic TryParse recently. Here's what I came up with;

public static T? TryParse<T>(string value, TryParseHandler<T> handler) where T : struct
{
    if (String.IsNullOrEmpty(value))
        return null;
    T result;
    if (handler(value, out result))
        return result;
    Trace.TraceWarning("Invalid value '{0}'", value);
    return null;
}

public delegate bool TryParseHandler<T>(string value, out T result);

Then it's simply a matter of calling thusly:

var value = TryParse<int>("123", int.TryParse);
var value2 = TryParse<decimal>("123.123", decimal.TryParse);


回答3:

Using try/catches for flow control is a terrible policy. Throw an exception causes performance lags while the runtime works around the exception. Instead validate the data before converting.

var attemptedValue = "asdfasdsd";
var type = typeof(int);
var converter = TypeDescriptor.GetConverter(type);
if (converter != null &&  converter.IsValid(attemptedValue))
    return converter.ConvertFromString(attemptedValue);
else
    return Activator.CreateInstance(type);


回答4:

If you are set on using TryParse, you can use reflection and do it like this:

public static bool Is<T>(this string input)
{
    var type = typeof (T);
    var temp = default(T);
    var method = type.GetMethod(
        "TryParse",
        new[]
            {
                typeof (string),
                Type.GetType(string.Format("{0}&", type.FullName))
            });
    return (bool) method.Invoke(null, new object[] {input, temp});
}


回答5:

This uses a static constructor for each generic type, so it only has to do the expensive work the first time you call it on a given type. It handles all the types in the system namespace which have TryParse methods. It also works with nullable versions of each of those (that are structs) except for enumerations.

    public static bool TryParse<t>(this string Value, out t result)
    {
        return TryParser<t>.TryParse(Value.SafeTrim(), out result);
    }
    private delegate bool TryParseDelegate<t>(string value, out t result);
    private static class TryParser<T>
    {
        private static TryParseDelegate<T> parser;
        // Static constructor:
        static TryParser()
        {
            Type t = typeof(T);
            if (t.IsEnum)
                AssignClass<T>(GetEnumTryParse<T>());
            else if (t == typeof(bool) || t == typeof(bool?))
                AssignStruct<bool>(bool.TryParse);
            else if (t == typeof(byte) || t == typeof(byte?))
                AssignStruct<byte>(byte.TryParse);
            else if (t == typeof(short) || t == typeof(short?))
                AssignStruct<short>(short.TryParse);
            else if (t == typeof(char) || t == typeof(char?))
                AssignStruct<char>(char.TryParse);
            else if (t == typeof(int) || t == typeof(int?))
                AssignStruct<int>(int.TryParse);
            else if (t == typeof(long) || t == typeof(long?))
                AssignStruct<long>(long.TryParse);
            else if (t == typeof(sbyte) || t == typeof(sbyte?))
                AssignStruct<sbyte>(sbyte.TryParse);
            else if (t == typeof(ushort) || t == typeof(ushort?))
                AssignStruct<ushort>(ushort.TryParse);
            else if (t == typeof(uint) || t == typeof(uint?))
                AssignStruct<uint>(uint.TryParse);
            else if (t == typeof(ulong) || t == typeof(ulong?))
                AssignStruct<ulong>(ulong.TryParse);
            else if (t == typeof(decimal) || t == typeof(decimal?))
                AssignStruct<decimal>(decimal.TryParse);
            else if (t == typeof(float) || t == typeof(float?))
                AssignStruct<float>(float.TryParse);
            else if (t == typeof(double) || t == typeof(double?))
                AssignStruct<double>(double.TryParse);
            else if (t == typeof(DateTime) || t == typeof(DateTime?))
                AssignStruct<DateTime>(DateTime.TryParse);
            else if (t == typeof(TimeSpan) || t == typeof(TimeSpan?))
                AssignStruct<TimeSpan>(TimeSpan.TryParse);
            else if (t == typeof(Guid) || t == typeof(Guid?))
                AssignStruct<Guid>(Guid.TryParse);
            else if (t == typeof(Version))
                AssignClass<Version>(Version.TryParse);
        }
        private static void AssignStruct<t>(TryParseDelegate<t> del)
            where t: struct
        {
            TryParser<t>.parser = del;
            if (typeof(t).IsGenericType
                && typeof(t).GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                return;
            }
            AssignClass<t?>(TryParseNullable<t>);
        }
        private static void AssignClass<t>(TryParseDelegate<t> del)
        {
            TryParser<t>.parser = del;
        }
        public static bool TryParse(string Value, out T Result)
        {
            if (parser == null)
            {
                Result = default(T);
                return false;
            }
            return parser(Value, out Result);
        }
    }

    private static bool TryParseEnum<t>(this string Value, out t result)
    {
        try
        {
            object temp = Enum.Parse(typeof(t), Value, true);
            if (temp is t)
            {
                result = (t)temp;
                return true;
            }
        }
        catch
        {
        }
        result = default(t);
        return false;
    }
    private static MethodInfo EnumTryParseMethod;
    private static TryParseDelegate<t> GetEnumTryParse<t>()
    {
        Type type = typeof(t);

        if (EnumTryParseMethod == null)
        {
            var methods = typeof(Enum).GetMethods(
                BindingFlags.Public | BindingFlags.Static);
            foreach (var method in methods)
                if (method.Name == "TryParse"
                    && method.IsGenericMethodDefinition
                    && method.GetParameters().Length == 2
                    && method.GetParameters()[0].ParameterType == typeof(string))
                {
                    EnumTryParseMethod = method;
                    break;
                }
        }
        var result = Delegate.CreateDelegate(
            typeof(TryParseDelegate<t>),
            EnumTryParseMethod.MakeGenericMethod(type), false)
            as TryParseDelegate<t>;
        if (result == null)
            return TryParseEnum<t>;
        else
            return result;
    }

    private static bool TryParseNullable<t>(string Value, out t? Result)
        where t: struct
    {
        t temp;
        if (TryParser<t>.TryParse(Value, out temp))
        {
            Result = temp;
            return true;
        }
        else
        {
            Result = null;
            return false;
        }
    }


回答6:

You can't do it on general types.

What you could do is to create an interface ITryParsable and use it for custom types that implement this interface.

I guess though that you intend to use this with basic types like int and DateTime. You can't change these types to implement new interfaces.



回答7:

How about something like this?

http://madskristensen.net/post/Universal-data-type-checker.aspx (Archive)

/// <summary> 
/// Checks the specified value to see if it can be 
/// converted into the specified type. 
/// <remarks> 
/// The method supports all the primitive types of the CLR 
/// such as int, boolean, double, guid etc. as well as other 
/// simple types like Color and Unit and custom enum types. 
/// </remarks> 
/// </summary> 
/// <param name="value">The value to check.</param> 
/// <param name="type">The type that the value will be checked against.</param> 
/// <returns>True if the value can convert to the given type, otherwise false. </returns> 
public static bool CanConvert(string value, Type type) 
{ 
    if (string.IsNullOrEmpty(value) || type == null) return false;
    System.ComponentModel.TypeConverter conv = System.ComponentModel.TypeDescriptor.GetConverter(type);
    if (conv.CanConvertFrom(typeof(string)))
    { 
        try 
        {
            conv.ConvertFrom(value); 
            return true;
        } 
        catch 
        {
        } 
     } 
     return false;
  }

This can be converted to a generic method pretty easily.

 public static bool Is<T>(this string value)
 {
    if (string.IsNullOrEmpty(value)) return false;
    var conv = System.ComponentModel.TypeDescriptor.GetConverter(typeof(T));

    if (conv.CanConvertFrom(typeof(string)))
    { 
        try 
        {
            conv.ConvertFrom(value); 
            return true;
        } 
        catch 
        {
        } 
     } 
     return false;
}


回答8:

Inspired by the solution posted here by Charlie Brown, I created a generic TryParse using reflection that optionally outputs the parsed value:

/// <summary>
/// Tries to convert the specified string representation of a logical value to
/// its type T equivalent. A return value indicates whether the conversion
/// succeeded or failed.
/// </summary>
/// <typeparam name="T">The type to try and convert to.</typeparam>
/// <param name="value">A string containing the value to try and convert.</param>
/// <param name="result">If the conversion was successful, the converted value of type T.</param>
/// <returns>If value was converted successfully, true; otherwise false.</returns>
public static bool TryParse<T>(string value, out T result) where T : struct {
    var tryParseMethod = typeof(T).GetMethod("TryParse", BindingFlags.Static | BindingFlags.Public, null, new [] { typeof(string), typeof(T).MakeByRefType() }, null);
    var parameters = new object[] { value, null };

    var retVal = (bool)tryParseMethod.Invoke(null, parameters);

    result = (T)parameters[1];
    return retVal;
}

/// <summary>
/// Tries to convert the specified string representation of a logical value to
/// its type T equivalent. A return value indicates whether the conversion
/// succeeded or failed.
/// </summary>
/// <typeparam name="T">The type to try and convert to.</typeparam>
/// <param name="value">A string containing the value to try and convert.</param>
/// <returns>If value was converted successfully, true; otherwise false.</returns>
public static bool TryParse<T>(string value) where T : struct {
    T throwaway;
    var retVal = TryParse(value, out throwaway);
    return retVal;
}

It can be called thus:

string input = "123";
decimal myDecimal;

bool myIntSuccess = TryParse<int>(input);
bool myDecimalSuccess = TryParse<decimal>(input, out myDecimal);

Update:
Also thanks to YotaXP's solution which I really like, I created a version that doesn't use extension methods but still has a singleton, minimizing the need to do reflection:

/// <summary>
/// Provides some extra parsing functionality for value types.
/// </summary>
/// <typeparam name="T">The value type T to operate on.</typeparam>
public static class TryParseHelper<T> where T : struct {
    private delegate bool TryParseFunc(string str, out T result);

    private static TryParseFunc tryParseFuncCached;

    private static TryParseFunc tryParseCached {
        get {
            return tryParseFuncCached ?? (tryParseFuncCached = Delegate.CreateDelegate(typeof(TryParseFunc), typeof(T), "TryParse") as TryParseFunc);
        }
    }

    /// <summary>
    /// Tries to convert the specified string representation of a logical value to
    /// its type T equivalent. A return value indicates whether the conversion
    /// succeeded or failed.
    /// </summary>
    /// <param name="value">A string containing the value to try and convert.</param>
    /// <param name="result">If the conversion was successful, the converted value of type T.</param>
    /// <returns>If value was converted successfully, true; otherwise false.</returns>
    public static bool TryParse(string value, out T result) {
        return tryParseCached(value, out result);
    }

    /// <summary>
    /// Tries to convert the specified string representation of a logical value to
    /// its type T equivalent. A return value indicates whether the conversion
    /// succeeded or failed.
    /// </summary>
    /// <param name="value">A string containing the value to try and convert.</param>
    /// <returns>If value was converted successfully, true; otherwise false.</returns>
    public static bool TryParse(string value) {
        T throwaway;
        return TryParse(value, out throwaway);
    }
}

Call it like this:

string input = "987";
decimal myDecimal;

bool myIntSuccess = TryParseHelper<int>.TryParse(input);
bool myDecimalSuccess = TryParseHelper<decimal>.TryParse(input, out myDecimal);


回答9:

When I wanted to do almost this exact thing, I had to implement it the hard way, given reflection. Given T, reflect on typeof(T) and look for a TryParse or Parse method, invoking it if you've found it.



回答10:

This is my try. I did it as an "exercise". I tried to make it as similar to use as the existing "Convert.ToX()"-ones etc. But this one is extension method:

    public static bool TryParse<T>(this String str, out T parsedValue)
    {
        try
        {
            parsedValue = (T)Convert.ChangeType(str, typeof(T));
            return true;
        }

        catch { parsedValue = default(T); return false; }
    }


回答11:

Quite a bit late to the party, but here's what I came up with. No exceptions, one-time (per type) reflection.

public static class Extensions {
    public static T? ParseAs<T>(this string str) where T : struct {
        T val;
        return GenericHelper<T>.TryParse(str, out val) ? val : default(T?);
    }
    public static T ParseAs<T>(this string str, T defaultVal) {
        T val;
        return GenericHelper<T>.TryParse(str, out val) ? val : defaultVal;
    }

    private static class GenericHelper<T> {
        public delegate bool TryParseFunc(string str, out T result);

        private static TryParseFunc tryParse;
        public static TryParseFunc TryParse {
            get {
                if (tryParse == null)
                    tryParse = Delegate.CreateDelegate(
                        typeof(TryParseFunc), typeof(T), "TryParse") as TryParseFunc;
                return tryParse;
            }
        }
    }
}

The extra class is required because extention methods are not permitted inside generic classes. This allows simple usage, as shown below, and only hits reflection the first time a type is used.

"5643".ParseAs<int>()


回答12:

Here's another option.

I wrote a class that makes it easy to register any number of TryParse handlers. It lets me do this:

var tp = new TryParser();

tp.Register<int>(int.TryParse);
tp.Register<decimal>(decimal.TryParse);
tp.Register<double>(double.TryParse);

int x;
if (tp.TryParse("42", out x))
{
    Console.WriteLine(x);
};

I get 42 printed to the console.

The class is:

public class TryParser
{
    public delegate bool TryParseDelegate<T>(string s, out T result);

    private Dictionary<Type, Delegate> _tryParsers = new Dictionary<Type, Delegate>();

    public void Register<T>(TryParseDelegate<T> d)
    {
        _tryParsers[typeof(T)] = d;
    }

    public bool Deregister<T>()
    {
        return _tryParsers.Remove(typeof(T));
    }

    public bool TryParse<T>(string s, out T result)
    {
        if (!_tryParsers.ContainsKey(typeof(T)))
        {
            throw new ArgumentException("Does not contain parser for " + typeof(T).FullName + ".");
        }
        var d = (TryParseDelegate<T>)_tryParsers[typeof(T)];
        return d(s, out result);
    }
}


回答13:

As you said, TryParse is not part of an interface. It is also not a member of any given base class since it's actually static and static functions can't be virtual. So, the compiler has no way of assuring that T actually has a member called TryParse, so this doesn't work.

As @Mark said, you could create your own interface and use custom types, but you're out of luck for the built-in types.



回答14:

This is a question of 'generic constraints'. Because you don't have a specific interface then you are stuck unless you follow the suggestions of the previous answer.

For documentation on this, check the following link:

http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx

It shows you how to use these constraints and should give you some more clues.



回答15:

Borrowed from http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx

when following this reference: How to invoke static method in C#4.0 with dynamic type?

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace Utils
{
   public class StaticMembersDynamicWrapper : DynamicObject
   {
      private Type _type;

      public StaticMembersDynamicWrapper(Type type) { _type = type; }

      // Handle static methods
      public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
      {
         var methods = _type
            .GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public)
            .Where(methodInfo => methodInfo.Name == binder.Name);

         var method = methods.FirstOrDefault();
         if (method != null)
         {
            result = method.Invoke(null, args);
            return true;
         }

         result = null;
         return false;
      }
   }

   public static class StaticMembersDynamicWrapperExtensions
   {
      static Dictionary<Type, DynamicObject> cache =
         new Dictionary<Type, DynamicObject>
         {
            {typeof(double), new StaticMembersDynamicWrapper(typeof(double))},
            {typeof(float), new StaticMembersDynamicWrapper(typeof(float))},
            {typeof(uint), new StaticMembersDynamicWrapper(typeof(uint))},
            {typeof(int), new StaticMembersDynamicWrapper(typeof(int))},
            {typeof(sbyte), new StaticMembersDynamicWrapper(typeof(sbyte))}
         };

      /// <summary>
      /// Allows access to static fields, properties, and methods, resolved at run-time.
      /// </summary>
      public static dynamic StaticMembers(this Type type)
      {
         DynamicObject retVal;
         if (!cache.TryGetValue(type, out retVal))
            return new StaticMembersDynamicWrapper(type);

         return retVal;
      }
   }
}

And use it as follows:

  public static T? ParseNumeric<T>(this string str, bool throws = true)
     where T : struct
  {
     var statics = typeof(T).StaticMembers();

     if (throws) return statics.Parse(str);

     T retval;
     if (!statics.TryParse(str, out retval)) return null;

     return retval;
  }


回答16:

I managed to get something that works like this

    var result = "44".TryParse<int>();

    Console.WriteLine( "type={0}, value={1}, valid={2}",        
    result.Value.GetType(), result.Value, result.IsValid );

Here's my code

 public static class TryParseGeneric
    {
        //extend int
        public static dynamic TryParse<T>( this string input )
        {    
            dynamic runner = new StaticMembersDynamicWrapper( typeof( T ) );

            T value;
            bool isValid = runner.TryParse( input, out value );
            return new { IsValid = isValid, Value = value };
        }
    }


    public class StaticMembersDynamicWrapper : DynamicObject
    {
        private readonly Type _type;
        public StaticMembersDynamicWrapper( Type type ) { _type = type; }

        // Handle static properties
        public override bool TryGetMember( GetMemberBinder binder, out object result )
        {
            PropertyInfo prop = _type.GetProperty( binder.Name, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public );
            if ( prop == null )
            {
                result = null;
                return false;
            }

            result = prop.GetValue( null, null );
            return true;
        }

        // Handle static methods
        public override bool TryInvokeMember( InvokeMemberBinder binder, object [] args, out object result )
        {
            var methods = _type
            .GetMethods( BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public ).Where( methodInfo => methodInfo.Name == binder.Name );

            var method = methods.FirstOrDefault();

            if ( method == null )
            {
                result = null;

                return false;
            }

            result = method.Invoke( null, args );

            return true;
        }
    }

The StaticMembersDynamicWrapper is adapted from David Ebbo's article (it was throwing an AmbiguousMatchException)



回答17:

public static T Get<T>(string val)
{ 
    return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromInvariantString(val);
}


回答18:

A version for getting descendants from XDocument.

public static T Get<T>(XDocument xml, string descendant, T @default)
{
    try
    {
        var converter = TypeDescriptor.GetConverter(typeof (T));
        if (converter != null)
        {
            return (T) converter.ConvertFromString(xml.Descendants(descendant).Single().Value);
        }
        return @default;
    }
    catch
    {
        return @default;
    }
}


标签: