Genérico TryParse

196

Estoy tratando de crear una extensión genérica que use 'TryParse' para verificar si una cadena es de un tipo dado:

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

esto no se compilará ya que no puede resolver el símbolo 'TryParse'

Según tengo entendido, 'TryParse' no es parte de ninguna interfaz.

¿Es esto posible hacer en absoluto?

Actualizar:

Usando las respuestas a continuación, se me ocurrió:

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

    return true;
}

Funciona bastante bien, pero creo que usar excepciones de esa manera no me parece correcto.

Actualización2:

Modificado para pasar el tipo en lugar de usar genéricos:

public static bool Is(this string input, Type targetType)
{
    try
    {
        TypeDescriptor.GetConverter(targetType).ConvertFromString(input);
        return true;
    }
    catch
    {
        return false;
    }
}
Muelles Myers
fuente
1
Creo que en este caso general solo tendrás que lidiar con la excepción kludge. puede agregar casos para verificar cosas como entradas o dobles y luego usar los métodos específicos de TryParse, pero aún tendrá que recurrir a esto para atrapar otros tipos.
luke
1
El uso del genérico es innecesario. Simplemente pase el Tipo como parámetro. public static bool Is (esta entrada de cadena, Tipo targetType). De esa manera, llamarlo parece un poco más bonito: x.Is (typeof (int)) -VS- x.Is <int> ()
mikesigs
2
Hay un método IsValid en el convertidor para que compruebe si la conversión tendrá problemas. Utilicé el siguiente método y parece funcionar bien. protected Boolean TryParse<T>(Object value, out T result) { result = default(T); var convertor = TypeDescriptor.GetConverter(typeof(T)); if (convertor == null || !convertor.IsValid(value)) { return false; } result = (T)convertor.ConvertFrom(value); return true; }
CastroXXL
@CastroXXL Gracias por mostrar interés en esta pregunta, sin embargo, su método no funcionaría, ya que quería verificar si el valor de la cadena era de cierto tipo en lugar de un objeto, aunque su método sería útil para los tipos de objetos (pero tiene que envolver el ConvertFrom(value)método en un try-catchbloque para detectar las excepciones.
Piers Myers
2
Debe verificar si (targetType == null) porque el primer uso de él en su código podría arrojar, pero esa excepción se tragaría por su captura.
Nick Strupat

Respuestas:

183

Debe usar la clase TypeDescriptor :

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);
    }
}
luke
fuente
3
Lamento resucitar, pero ¿GetConverter devuelve nulo? Creo que si lo hiciera, entonces probablemente debería lanzarse una excepción en lugar de fallar esencialmente en silencio y devolver algo más. Cuando lo probé en mi propia clase (en la que no definí un convertidor de tipos), obtuve un convertidor de GetConverter, pero luego ConvertFromString arrojó una NotSupportedException.
user420667
3
@ user420667, creo que debería verificar el resultado de CanConvertFrom (typeof (string)) antes de intentar convertir de string. TypeConverter puede no admitir la conversión de una cadena.
Reuben Bond el
3
Puede agregar if (typeof (T) .IsEnum) {return (T) Enum.Parse (typeof (T), input); } [como un acceso directo bastante genérico para todos los tipos Enum] antes de obtener el Convertidor. Supongo que depende de la frecuencia con la que haga tipos Enum en lugar de los tipos más complejos.
Jesse Chisholm
10
No entiendo por qué esto se marca como la respuesta y se vota tanto cuando no implementa lo que se solicitó: un Try Parse genérico . El objetivo principal de los métodos TryParse es que no arrojan excepciones al intentar realizar el análisis y tienen un impacto mucho menor en el rendimiento cuando el análisis falla y esta solución no proporciona exactamente eso.
Florin Dumitrescu
2
Un problema con esto es que si T es un int y la entrada es mayor que int.MaxValue, arrojará un System.Exception w / System.OverFlowException como la excepción interna. Entonces, si espera una Excepción de desbordamiento, no la obtendrá a menos que interrogue a la Excepción lanzada. La razón es que ConvertFromString arroja una Excepción de desbordamiento, y luego la conversión a T arroja una Excepción de sistema.
Trevor
78

También solicité un TryParse genérico recientemente. Esto es lo que se me ocurrió;

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

Entonces es simplemente una cuestión de llamar así:

var value = TryParse<int>("123", int.TryParse);
var value2 = TryParse<decimal>("123.123", decimal.TryParse);
Charlie Brown
fuente
3
Acabo de encontrar esta publicación nuevamente meses más tarde y noté mientras la usaba nuevamente que el método no puede inferirse Tdel controlador, y tenemos que especificar explícitamente Tcuándo lo llamamos. Tengo curiosidad, ¿por qué no puede inferir T?
Nick Strupat
25
¿Por qué querrías usar esta función? Si sabe a qué función llamar para analizar el valor, ¿por qué no simplemente llamarlo directamente? Ya conoce el tipo de entrada correcto y no hay necesidad de genéricos. Esta solución no funcionaría para los tipos sin un TryParseHandler.
xxbbcc
2
@xxbbcc: me gustaría usar esta función porque TryParse devuelve un valor booleano que indica si el análisis fue exitoso. Devuelve su valor analizado a través de un parámetro de salida. A veces solo quiero hacer algo como esto SomeMethod(TryParse<int>(DollarTextbox.Text, int.TryParse))sin crear una variable de salida para captar el resultado int.TryParse. Sin embargo, estoy de acuerdo con el sentimiento de Nick sobre hacer que la función infiera el tipo.
Walter Stabosz
1
Método muy eficiente Muy recomendable.
Vladimir Kocjancic
3
Recomendaría un valor predeterminado como tercer parámetro. Eso soluciona el problema donde T no se puede inferir. Además, le permite a uno decidir qué valor desean si el valor de la cadena no es válido. Por ejemplo, -1 podría significar inválido. public static T TryParse <T> (valor de cadena, controlador TryParseHandler <T>, T defaultValue)
Rhyous
33

Usar try / catches para controlar el flujo es una política terrible. Lanzar una excepción provoca retrasos en el rendimiento mientras el tiempo de ejecución funciona alrededor de la excepción. En su lugar, valide los datos antes de convertir.

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);
Sitios de pensamiento
fuente
2
Recibo un aviso de Resharper que converter != nullsiempre es cierto, por lo que se puede eliminar del código.
ErikE
55
@ErikE No siempre confío en esas advertencias de ReSharper. A menudo no pueden ver lo que sucede en tiempo de ejecución.
ProfK
1
@ProfK MSDN no dice que pueda devolver nulo msdn.microsoft.com/en-us/library/ewtxwhzx.aspx
danio
@danio Simplemente estaba compartiendo mi experiencia con tales advertencias de R # en general. Ciertamente no implicaba que estaba mal en este caso.
ProfK
14

Si está configurado con TryParse, puede usar la reflexión y hacerlo así:

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});
}
Joseph Sturtevant
fuente
Esto es muy bueno, y elimina las excepciones que no me gustaron de todos modos. Sin embargo, todavía es un poco complicado.
Piers Myers
66
Buena solución, pero cualquier respuesta que implique reflexión (especialmente en un método de utilidad que podría llamarse fácilmente desde un bucle interno) necesita un descargo de responsabilidad sobre el rendimiento. Ver: stackoverflow.com/questions/25458/how-costly-is-net-reflection
Patrick M
Suspiro. Entonces, las opciones son (1) usar excepciones para el control del flujo de código, (2) usar la reflexión con sus costos de velocidad. Estoy de acuerdo con @PiersMyers: ninguna opción es ideal. Lo bueno es que ambos trabajan. :)
Jesse Chisholm
Creo que puedes reemplazar el Type.GetType(string.Format(...))con type.MakeByRefType().
Drew Noakes
3
El método solo debe reflejarse una vez por tipo, no una vez por llamada. Si convierte esto en una clase genérica con una variable miembro estática, puede reutilizar la salida de la primera reflexión.
Andrew Hill
7

Esto usa un constructor estático para cada tipo genérico, por lo que solo tiene que hacer el trabajo costoso la primera vez que lo llama en un tipo dado. Maneja todos los tipos en el espacio de nombres del sistema que tienen métodos TryParse. También funciona con versiones anulables de cada una de ellas (que son estructuras) a excepción de las enumeraciones.

    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;
        }
    }
Bryce Wagner
fuente
6

¿Qué tal algo como esto?

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

/// <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;
  }

Esto se puede convertir a un método genérico con bastante facilidad.

 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;
}
Beto
fuente
¿Importa si devuelve verdadero desde el bloque try o devuelve falso desde el bloque catch? Supongo que no, pero sigo pensando que el uso de excepciones de esta forma se siente mal a mí ...
Muelles Myers
3
No importa si regresa del bloque catch, esto es lo mismo. por cierto. Por lo general, es malo tener una cláusula de captura genérica: catch { }. Sin embargo, en este caso no hay alternativa, porque .NET BaseNumberConverterarroja la Exceptionclase base en caso de un error de conversión. Esto es muy desafortunado. De hecho, todavía hay bastantes lugares donde se lanza este tipo base. Esperemos que Microsoft los arregle en una versión futura del marco.
Steven
Gracias Steven, no podría haberlo dicho mejor.
Bob
No se utiliza el resultado de la conversión: el código es redundante.
BillW
4

No puedes hacerlo en tipos generales.

Lo que puede hacer es crear una interfaz ITryParsable y usarla para tipos personalizados que implementan esta interfaz.

Sin embargo, supongo que tienes la intención de usar esto con tipos básicos como inty DateTime. No puede cambiar estos tipos para implementar nuevas interfaces.

Mark Byers
fuente
1
Me pregunto si eso funcionaría usando la palabra clave dinámica en .net 4.
Pierre-Alain Vigeant
@Pierre: Esto no funcionará de manera predeterminada en C # con la dynamicpalabra clave, porque no funcionará en la escritura estática. Puede crear su propio objeto dinámico que pueda manejar esto, pero no es el predeterminado.
Steven
4

Inspirado por la solución publicada aquí por Charlie Brown, creé un TryParse genérico utilizando una reflexión que opcionalmente genera el valor analizado:

/// <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;
}

Se le puede llamar así:

string input = "123";
decimal myDecimal;

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

Actualización:
también gracias a la solución de YotaXP que realmente me gusta, creé una versión que no usa métodos de extensión pero que aún tiene un singleton, minimizando la necesidad de hacer una reflexión:

/// <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);
    }
}

Llámalo así:

string input = "987";
decimal myDecimal;

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

Bastante tarde para la fiesta, pero esto es lo que se me ocurrió. Sin excepciones, reflexión única (por tipo).

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;
            }
        }
    }
}

La clase adicional es necesaria porque los métodos de extensión no están permitidos dentro de las clases genéricas. Esto permite un uso simple, como se muestra a continuación, y solo alcanza el reflejo la primera vez que se usa un tipo.

"5643".ParseAs<int>()
YotaXP
fuente
3

Aquí hay otra opción.

Escribí una clase que facilita el registro de cualquier número de TryParsecontroladores. Me permite hacer esto:

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);
};

Me 42imprimen en la consola.

La clase es:

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);
    }
}
Enigmatividad
fuente
Me gusta esto, pero ¿cómo lo harías sin genéricos? El caso de uso es reflejo, por supuesto.
Sinaesthetic
Agregué un método sobrecargado que hace algunos hackers de reflexión. Si hay una forma más elegante de abordarlo, todos son ojos lol gist.github.com/dasjestyr/90d8ef4dea179a6e08ddd85e0dacbc94
Sinaesthetic
2

Cuando quise hacer casi exactamente esto, tuve que implementarlo de la manera difícil, dada la reflexión. Dado T, reflexione typeof(T)y busque un método TryParseo Parse, invocándolo si lo ha encontrado.

JSB ձոգչ
fuente
Esto es lo que iba a sugerir.
Steven Evers
2

Este es mi intento. Lo hice como un "ejercicio". Traté de hacerlo tan similar al uso como los existentes " Convert.ToX () " -ones, etc. Pero este es el método de extensión:

    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; }
    }
W0lfw00ds
fuente
La principal desventaja de esto en comparación con TypeConverter.ConvertFrom()es que la clase fuente tiene que proporcionar la conversión de tipo, lo que generalmente significa que no puede admitir la conversión a tipos personalizados.
Ian Goldby
1

Como dijiste, TryParseno es parte de una interfaz. Tampoco es miembro de ninguna clase base dada, ya que es realmente staticy las staticfunciones no pueden serlo virtual. Por lo tanto, el compilador no tiene forma de asegurar que en Trealidad tiene un miembro llamado TryParse, por lo que esto no funciona.

Como dijo @Mark, podría crear su propia interfaz y usar tipos personalizados, pero no tiene suerte para los tipos incorporados.

Donnie
fuente
1
public static class Primitive
{
    public static DateTime? TryParseExact(string text, string format, IFormatProvider formatProvider = null, DateTimeStyles? style = null)
    {
        DateTime result;
        if (DateTime.TryParseExact(text, format, formatProvider, style ?? DateTimeStyles.None, out result))
            return result;
        return null;
    }

    public static TResult? TryParse<TResult>(string text) where TResult : struct
    {
        TResult result;
        if (Delegates<TResult>.TryParse(text, out result))
            return result;
        return null;
    }

    public static bool TryParse<TResult>(string text, out TResult result) => Delegates<TResult>.TryParse(text, out result);

    public static class Delegates<TResult>
    {
        private delegate bool TryParseDelegate(string text, out TResult result);

        private static readonly TryParseDelegate _parser = (TryParseDelegate)Delegate.CreateDelegate(typeof(TryParseDelegate), typeof(TResult), "TryParse");

        public static bool TryParse(string text, out TResult result) => _parser(text, out result);
    }
}
ChaosPandion
fuente
0

Esta es una cuestión de 'restricciones genéricas'. Debido a que no tiene una interfaz específica, está atascado a menos que siga las sugerencias de la respuesta anterior.

Para obtener documentación al respecto, consulte el siguiente enlace:

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

Le muestra cómo usar estas restricciones y debería darle algunas pistas más.

Simón
fuente
0

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

al seguir esta referencia: ¿Cómo invocar el método estático en C # 4.0 con tipo dinámico?

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;
      }
   }
}

Y úsalo de la siguiente manera:

  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;
  }
GregC
fuente
0

Logré conseguir algo que funciona así

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

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

Aquí está mi código

 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;
        }
    }

StaticMembersDynamicWrapper está adaptado del artículo de David Ebbo (arrojaba una AmbiguousMatchException)


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

Con el TypeDescriptoruso de la clase de TryParsemanera relacionada:

public static bool TryParse<T>(this string input, out T parsedValue)
{
    parsedValue = default(T);
    try
    {
        var converter = TypeDescriptor.GetConverter(typeof(T));
        parsedValue = (T)converter.ConvertFromString(input);
        return true;
    }
    catch (NotSupportedException)
    {
        return false;
    }
}
Vova
fuente
Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación, y probablemente resultaría en más votos positivos. Recuerde que está respondiendo la pregunta para los lectores en el futuro, no solo la persona que pregunta ahora. Por favor, editar su respuesta para agregar explicaciones y dar una indicación de lo que se aplican limitaciones y supuestos.
pitido doble
0

Usando la información anterior, esto es lo que desarrollé. Convertirá el objeto directamente es posible, de lo contrario, convertirá el objeto en una cadena y llamará al método TryParse para el tipo de objeto deseado.

Guardo en caché los métodos en un diccionario, ya que cada uno se encuentra para reducir la carga de obtención de métodos.

Es posible probar si el objeto se puede convertir directamente al tipo de destino, lo que reduciría aún más la parte de conversión de cadena. Pero lo dejaré fuera por ahora.

    /// <summary>
    /// Used to store TryParse converter methods
    /// </summary>
    private static readonly Dictionary<Type, MethodInfo> TypeConverters = new Dictionary<Type, MethodInfo>();

    /// <summary>
    /// Attempt to parse the input object to the output type
    /// </summary>
    /// <typeparam name="T">output type</typeparam>
    /// <param name="obj">input object</param>
    /// <param name="result">output result on success, default(T) on failure</param>
    /// <returns>Success</returns>
    public static bool TryParse<T>([CanBeNull] object obj, out T result)
    {
        result = default(T);

        try
        {
            switch (obj)
            {
                // don't waste time on null objects
                case null: return false;

                // if the object is already of type T, just return the value
                case T val:
                    result = val;
                    return true;
            }

            // convert the object into type T via string conversion
            var input = ((obj as string) ?? obj.ToString()).Trim();
            if (string.IsNullOrEmpty(input)) return false;

            var type = typeof (T);
            Debug.WriteLine($"Info: {nameof(TryParse)}<{type.Name}>({obj.GetType().Name}=\"{input}\")");

            if (! TypeConverters.TryGetValue(type, out var method))
            {
                // get the TryParse method for this type
                method = type.GetMethod("TryParse",
                    new[]
                    {
                        typeof (string),
                        Type.GetType($"{type.FullName}&")
                    });

                if (method is null)
                    Debug.WriteLine($"FAILED: Cannot get method for {type.Name}.TryParse()");

                // store it so we don't have to do this again
                TypeConverters.Add(type, method);
            }

            // have to keep a reference to parameters if you want to get the returned ref value
            var parameters = new object[] {input, null};
            if ((bool?) method?.Invoke(null, parameters) == true)
            {
                result = (T) parameters[1];
                return true;
            }                
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
        }

        return false;
    }
B Duffy
fuente
Tuve que agregar otra función para admitir enumeraciones. Parece que el análisis Enum requiere el atributo "where T: struct", y quiero que esto funcione en cualquier cosa convertible. (probablemente debería agregar un atributo convertible al tipo). Sin embargo, algunas de las siguientes sugerencias parecen más simples (por lo tanto, mejores).
B Duffy
0

Puse un montón de ideas aquí juntas y terminé con una solución muy corta.

Este es un método de extensión en una cadena

enter code here

Lo hice con la misma huella que los métodos TryParse en los tipos numéricos

    /// <summary>
    /// string.TryParse()
    /// 
    /// This generic extension method will take a string
    ///     make sure it is not null or empty
    ///     make sure it represents some type of number e.g. "123" not "abc"
    ///     It then calls the appropriate converter for the type of T
    /// </summary>
    /// <typeparam name="T">The type of the desired retrunValue e.g. int, float, byte, decimal...</typeparam>
    /// <param name="targetText">The text to be converted</param>
    /// <param name="returnValue">a populated value of the type T or the default(T) value which is likely to be 0</param>
    /// <returns>true if the string was successfully parsed and converted otherwise false</returns>
    /// <example>
    /// float testValue = 0;
    ///  if ( "1234".TryParse<float>( out testValue ) )
    ///  {
    ///      doSomethingGood();
    ///  }
    ///  else
    ///  {
    ///      handleTheBadness();
    ///  }
    /// </example>
    public static bool TryParse<T>(this string targetText, out T returnValue )
    {
        bool returnStatus = false;

        returnValue = default(T);

        //
        // make sure the string is not null or empty and likely a number...
        // call whatever you like here or just leave it out - I would
        // at least make sure the string was not null or empty  
        //
        if ( ValidatedInputAnyWayYouLike(targetText) )
        {

            //
            // try to catch anything that blows up in the conversion process...
            //
            try
            {
                var type = typeof(T);
                var converter = TypeDescriptor.GetConverter(type);

                if (converter != null && converter.IsValid(targetText))
                {
                    returnValue = (T)converter.ConvertFromString(targetText);
                    returnStatus = true;
                }

            }
            catch
            {
                // just swallow the exception and return the default values for failure
            }

        }

        return (returnStatus);

    }

'' '

JD Hicks
fuente
flotante testValue = 0; if ("1234". TryParse <float> (out testValue)) {doSomethingGood (); } else {handleTheBadness (); }
JD Hicks
0

T. TryParse ... ¿por qué?

No veo ningún beneficio en tener esa TryParsefunción genérica . Hay demasiadas estrategias diferentes para analizar y convertir datos entre diferentes tipos, con un posible comportamiento conflictivo. ¿Cómo podría esta función saber qué estrategia elegir de una manera libre de contexto?

  • las clases con funciones dedicadas de TryParse podrían llamarse
  • Las clases con funciones Parse dedicadas se pueden envolver con el resultado try-catch y bool
  • clases con sobrecargas de operadores, ¿cómo les permitiría manejar el análisis?
  • Los descriptores de tipo se incorporan utilizando Convert.ChangeType. Esta API es personalizable en tiempo de ejecución. ¿Su función requiere un comportamiento predeterminado o permite la personalización?
  • ¿debería permitir que cualquier marco de mapeo intente analizar por usted?
  • ¿Cómo manejarías los conflictos en lo anterior?

fuente
-2

Una versión para obtener descendientes de 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;
    }
}
Andreas
fuente