Универсальный Метод Tryparse
Я пытаюсь создать универсальное расширение, которое использует 'TryParse', чтобы проверить, является ли строка заданным типом:
public static bool Is<T>(this string input)
{
T notUsed;
return T.TryParse(input, out notUsed);
}
это не будет компилироваться, так как он не может разрешить символ 'TryParse'
как я понимаю, 'TryParse' не является частью какого-либо интерфейса.
можно ли это вообще сделать?
обновление:
используя ответы ниже, я придумал:
public static bool Is<T>(this string input)
{
try
{
TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
}
catch
{
return false;
}
return true;
}
он работает довольно хорошо, но я думаю, что с помощью исключения в этом смысле не кажутся мне правильными.
Update2:
изменено для передачи типа, а не использовать дженерики:
public static bool Is(this string input, Type targetType)
{
try
{
TypeDescriptor.GetConverter(targetType).ConvertFromString(input);
return true;
}
catch
{
return false;
}
}
18 ответов:
вы должны использовать 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); } }
недавно мне также потребовался общий TryParse. Вот что я придумал;
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);
тогда это просто вопрос вызова так:
var value = TryParse<int>("123", int.TryParse); var value2 = TryParse<decimal>("123.123", decimal.TryParse);
использование try / catches для управления потоком-это ужасная политика. Бросок исключения приводит к задержкам производительности, в то время как среда выполнения работает вокруг исключения. Вместо этого проверьте данные перед преобразованием.
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);
Если вы настроены на использование TryParse, вы можете использовать отражение и сделать это следующим образом:
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}); }
Это использует статический конструктор для каждого универсального типа, поэтому он должен выполнять только дорогостоящую работу при первом вызове его для данного типа. Он обрабатывает все типы в пространстве имен system, которые имеют методы TryParse. Он также работает с nullable версиями каждого из них (которые являются структурами), за исключением перечислений.
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; } }
вы не можете сделать это на общих видах.
что вы могли бы сделать, это создать интерфейс ITryParsable и использовать его для пользовательских типов, которые реализуют этот интерфейс.
Я думаю, что вы собираетесь использовать это с основными типами, такими как
int
иDateTime
. Вы не можете изменить эти типы для реализации новых интерфейсов.
вдохновленный решением, опубликованным здесь Чарли Брауном, я создал общий TryParse, используя отражение, которое необязательно выводит анализируемое значение:
/// <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; }
Это можно назвать так:
string input = "123"; decimal myDecimal; bool myIntSuccess = TryParse<int>(input); bool myDecimalSuccess = TryParse<decimal>(input, out myDecimal);
обновление:
Также благодаря решению YotaXP, которое мне очень нравится, я создал версию, которая не использует методы расширения, но все еще имеет синглтон, сводя к минимуму необходимость делать отражение:/// <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); } }
называйте это как это:
string input = "987"; decimal myDecimal; bool myIntSuccess = TryParseHelper<int>.TryParse(input); bool myDecimalSuccess = TryParseHelper<decimal>.TryParse(input, out myDecimal);
Как насчет чего-то вроде этого?
http://madskristensen.net/post/Universal-data-type-checker.aspx (архиве)
/// <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; }
Это может быть преобразовано в общий метод довольно легко.
public static T Is<T>(this string input) { 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; }
когда я хотел сделать почти эту точную вещь, мне пришлось реализовать ее на жестком пути, учитывая отражение. Учитывая
T
, размышлять оtypeof(T)
искатьTryParse
илиParse
метод, вызывая его, если вы его нашли.
Это моя попытка. Я сделал это как"упражнение". Я попытался сделать его таким же похожим на использование, как и существующий"преобразовать.ToX () " - одни etc. Но это один метод расширения:
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; } }
совсем немного опоздал на вечеринку, но вот что я придумал. Без исключений, однократное (по типу) отражение.
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; } } } }
дополнительный класс необходим, потому что методы расширения не разрешены внутри универсальных классов. Это позволяет просто использовать, как показано ниже, и только попадает отражение при первом использовании типа.
"5643".ParseAs<int>()
вот еще один вариант.
Я написал класс, который легко зарегистрировать любое количество
TryParse
обработчики. Это позволяет мне сделать это: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); };
Я
42
печать на консоль.класс:
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); } }
как ты и сказал,
TryParse
не является частью интерфейса. Он также не является членом любого базового класса, так как это на самом делеstatic
иstatic
функции не могут бытьvirtual
. Таким образом, компилятор не может гарантировать, чтоT
на самом деле есть элемент под названиемTryParse
, Так это не работает.как сказал @Mark, вы можете создать свой собственный интерфейс и использовать пользовательские типы, но вам не повезло со встроенными типами.
Это вопрос общих ограничений. Поскольку у вас нет определенного интерфейса, вы застряли, если не будете следовать предложениям предыдущего ответа.
для получения документации по этому вопросу, проверьте следующую ссылку:
http://msdn.microsoft.com/en-us/library/ms379564 (VS. 80). aspx
Он показывает вам, как использовать эти ограничения и должен дать вам еще несколько подсказок.
заимствовано из http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx
когда по этой ссылке: как вызвать статический метод в C#4.0 с динамическим типом?
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; } } }
и использовать его следующим образом:
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; }
мне удалось получить что-то, что работает так
var result = "44".TryParse<int>(); Console.WriteLine( "type={0}, value={1}, valid={2}", result.Value.GetType(), result.Value, result.IsValid );
вот мой код
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 адаптирован из Дэвида Эббо статьи (он бросал AmbiguousMatchException)
public static T Get<T>(string val) { return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromInvariantString(val); }
версия для получения потомков от 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; } }