J'essaie de créer une extension générique qui utilise 'TryParse' pour vérifier si une chaîne est d'un type donné:
public static bool Is<T>(this string input)
{
T notUsed;
return T.TryParse(input, out notUsed);
}
cela ne compilera pas car il ne peut pas résoudre le symbole 'TryParse'
Si j'ai bien compris, 'TryParse' ne fait partie d'aucune interface.
Est-ce possible de le faire?
Mise à jour:
En utilisant les réponses ci-dessous, je suis venu avec:
public static bool Is<T>(this string input)
{
try
{
TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
}
catch
{
return false;
}
return true;
}
Cela fonctionne assez bien, mais je pense que l'utilisation d'exceptions de cette manière ne me semble pas appropriée.
pdate2:
Modifié pour passer le type plutôt que d'utiliser les génériques:
public static bool Is(this string input, Type targetType)
{
try
{
TypeDescriptor.GetConverter(targetType).ConvertFromString(input);
return true;
}
catch
{
return false;
}
}
Vous devriez utiliser la classe 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);
}
}
J'ai également eu besoin d'un TryParse générique récemment. Voici ce que je suis venu avec;
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);
Ensuite, il suffit d'appeler ainsi:
var value = TryParse<int>("123", int.TryParse);
var value2 = TryParse<decimal>("123.123", decimal.TryParse);
Utiliser try/catch pour contrôler le flux est une politique terrible. Le lancement d'une exception entraîne des retards de performances pendant que le moteur d'exécution contourne l'exception. Au lieu de cela, validez les données avant la conversion.
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);
Si vous souhaitez utiliser TryParse, vous pouvez utiliser la réflexion et procéder comme suit:
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});
}
Ceci utilise un constructeur statique pour chaque type générique, de sorte qu'il ne doit effectuer que le travail coûteux la première fois que vous l'appelez sur un type donné. Il gère tous les types dans l'espace de noms système qui ont des méthodes TryParse. Il fonctionne également avec les versions nullables de chacune de celles-ci (qui sont des structures) à l'exception des énumérations.
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;
}
}
Que diriez-vous quelque chose comme ça?
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;
}
Cela peut être converti en une méthode générique assez facilement.
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;
}
Vous ne pouvez pas le faire sur des types généraux.
Ce que vous pouvez faire est de créer une interface ITryParsable et de l’utiliser pour les types personnalisés qui implémentent cette interface.
Je suppose cependant que vous avez l’intention de l’utiliser avec des types de base comme int
et DateTime
. Vous ne pouvez pas modifier ces types pour implémenter de nouvelles interfaces.
Inspiré par la solution publiée ici par Charlie Brown, j'ai créé un TryParse générique à l'aide d'une réflexion générant éventuellement la valeur analysée:
/// <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;
}
On peut l'appeler ainsi:
string input = "123";
decimal myDecimal;
bool myIntSuccess = TryParse<int>(input);
bool myDecimalSuccess = TryParse<decimal>(input, out myDecimal);
Mise à jour:
Également grâce à la solution de YotaXP qui me plait beaucoup, j’ai créé une version qui n’utilise pas de méthodes d’extension, mais qui a toujours un singleton, minimisant ainsi le besoin de faire de la réflexion:
/// <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);
}
}
Appelez ça comme ça:
string input = "987";
decimal myDecimal;
bool myIntSuccess = TryParseHelper<int>.TryParse(input);
bool myDecimalSuccess = TryParseHelper<decimal>.TryParse(input, out myDecimal);
Voici une autre option.
J'ai écrit une classe qui facilite l'enregistrement d'un nombre quelconque de gestionnaires TryParse
. Cela me permet de faire ceci:
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);
};
Je reçois 42
imprimé sur la console.
Le cours est:
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);
}
}
Un peu tard pour la fête, mais voici ce que j'ai trouvé. Aucune exception, réflexion ponctuelle (par type).
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 classe supplémentaire est requise car les méthodes d'extention ne sont pas autorisées dans les classes génériques. Cela permet une utilisation simple, comme indiqué ci-dessous, et ne prend en compte la réflexion que la première fois qu'un type est utilisé.
"5643".ParseAs<int>()
Quand je voulais faire presque cette chose-là, je devais la mettre en œuvre à la dure, compte tenu de la réflexion. Étant donné T
, réfléchissez à typeof(T)
et cherchez une méthode TryParse
ou Parse
, en l'invoquant si vous l'avez trouvée.
C'est mon essai. Je l'ai fait comme un "exercice". J'ai essayé de le rendre aussi similaire à utiliser que l'existant "Convert.ToX ()" - etc. etc. Mais celui-ci est une méthode d'extension:
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; }
}
Comme vous l'avez dit, TryParse
ne fait pas partie d'une interface. De plus, il ne fait pas partie d'une classe de base donnée car il s'agit en réalité de static
et de static
, il ne peut s'agir de virtual
. Donc, le compilateur n'a aucun moyen de s'assurer que T
a réellement un membre appelé TryParse
, donc cela ne fonctionne pas.
Comme @Mark l'a dit, vous pouvez créer votre propre interface et utiliser des types personnalisés, mais vous n’avez pas de chance pour les types intégrés.
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);
}
}
C'est une question de "contraintes génériques". Parce que vous n'avez pas d'interface spécifique, vous êtes bloqué à moins de suivre les suggestions de la réponse précédente.
Pour la documentation à ce sujet, vérifiez le lien suivant:
http://msdn.Microsoft.com/en-us/library/ms379564 (VS.80) .aspx
Il vous montre comment utiliser ces contraintes et devrait vous donner quelques indices supplémentaires.
Emprunté de http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx
en suivant cette référence: Comment appeler une méthode statique en C # 4.0 avec un type dynamique?
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;
}
}
}
Et utilisez-le comme suit:
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;
}
Avec TypeDescriptor
utilisation de la classe de manière liée à TryParse
:
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;
}
}
public static T Get<T>(string val)
{
return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromInvariantString(val);
}
En utilisant les informations ci-dessus, voici ce que j'ai développé. Il est possible de convertir directement l'objet, sinon il convertit l'objet en chaîne et appelle la méthode TryParse pour le type d'objet souhaité.
Je mets en cache les méthodes dans un dictionnaire au fur et à mesure de leur rencontre afin de réduire la charge d'extraction de méthodes.
Il est possible de tester si l'objet peut être directement converti en type cible, ce qui réduirait encore la conversion de chaîne. Mais je laisserai ça pour l'instant.
/// <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;
}
J'ai réussi à obtenir quelque chose qui fonctionne comme ça
var result = "44".TryParse<int>();
Console.WriteLine( "type={0}, value={1}, valid={2}",
result.Value.GetType(), result.Value, result.IsValid );
Voici mon 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;
}
}
StaticMembersDynamicWrapper est adapté de David Ebbo article (il lançait une exception AmbiguousMatchException)