c# - toparse - tryparse syntax
TryParse genérico (13)
Aqui está outra opção.
Eu escrevi uma classe que facilita o registro de qualquer número de manipuladores TryParse
. Isso me permite fazer isso:
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);
};
Eu tenho 42
impressos no console.
A aula é:
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);
}
}
Eu estou tentando criar uma extensão genérica que usa 'TryParse' para verificar se uma seqüência de caracteres é um determinado tipo:
public static bool Is<T>(this string input)
{
T notUsed;
return T.TryParse(input, out notUsed);
}
isso não será compilado, pois não pode resolver o símbolo 'TryParse'
Pelo que entendi, 'TryParse' não faz parte de nenhuma interface.
Isso é possível fazer em tudo?
Atualizar:
Usando as respostas abaixo eu inventei:
public static bool Is<T>(this string input)
{
try
{
TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
}
catch
{
return false;
}
return true;
}
Funciona muito bem, mas acho que usar exceções dessa maneira não parece certo para mim.
Update2:
Modificado para passar o tipo em vez de usar genéricos:
public static bool Is(this string input, Type targetType)
{
try
{
TypeDescriptor.GetConverter(targetType).ConvertFromString(input);
return true;
}
catch
{
return false;
}
}
Como você disse, o TryParse
não faz parte de uma interface. Também não é membro de nenhuma classe base, uma vez que, na verdade, static
funções static
e static
não podem ser virtual
. Portanto, o compilador não tem como garantir que T
realmente tenha um membro chamado TryParse
, então isso não funciona.
Como o @Mark disse, você poderia criar sua própria interface e usar tipos personalizados, mas você está sem sorte para os tipos internos.
Esta é minha tentativa. Eu fiz isso como um "exercício". Eu tentei torná-lo tão parecido para usar como o existente " Convert.ToX () " -ones etc. Mas este é o método de extensão:
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; }
}
Esta é uma questão de "restrições genéricas". Como você não tem uma interface específica, fica preso, a menos que você siga as sugestões da resposta anterior.
Para documentação sobre isso, verifique o seguinte link:
http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx
Ele mostra como usar essas restrições e deve fornecer mais algumas dicas.
Eu também precisei de um TryParse genérico recentemente. Aqui está o que eu inventei;
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);
Então é simplesmente uma questão de chamar assim:
var value = TryParse<int>("123", int.TryParse);
var value2 = TryParse<decimal>("123.123", decimal.TryParse);
Inspirado pela solução postada aqui por Charlie Brown, criei um TryParse genérico usando a reflexão que, opcionalmente, gera o valor analisado:
/// <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;
}
Pode ser chamado assim:
string input = "123";
decimal myDecimal;
bool myIntSuccess = TryParse<int>(input);
bool myDecimalSuccess = TryParse<decimal>(input, out myDecimal);
Atualizar:
Também graças à solução do YotaXP que eu realmente gosto, eu criei uma versão que não usa métodos de extensão mas ainda tem um singleton, minimizando a necessidade de fazer reflexões:
/// <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);
}
}
Chame assim:
string input = "987";
decimal myDecimal;
bool myIntSuccess = TryParseHelper<int>.TryParse(input);
bool myDecimalSuccess = TryParseHelper<decimal>.TryParse(input, out myDecimal);
Quando eu queria fazer quase exatamente isso, eu tive que implementar da maneira mais difícil, dada a reflexão. Dado T
, reflita em typeof(T)
e procure por um método TryParse
ou Parse
, invocando-o se você o encontrou.
Que tal algo como isso?
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;
}
Isso pode ser convertido em um método genérico facilmente.
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;
}
Um pouco atrasado para a festa, mas aqui está o que eu fiz. Sem exceções, uma vez (por tipo) de reflexão.
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;
}
}
}
}
A classe extra é necessária porque os métodos de extensão não são permitidos dentro de classes genéricas. Isso permite um uso simples, conforme mostrado abaixo, e só atinge a reflexão na primeira vez em que um tipo é usado.
"5643".ParseAs<int>()
Uma versão para obter descendentes do 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;
}
}
Você deve usar a 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);
}
}
Você não pode fazer isso em tipos gerais.
O que você poderia fazer é criar uma interface ITryParsable e usá-la para tipos personalizados que implementam essa interface.
Eu acho que você pretende usar isso com tipos básicos como int
e DateTime
. Você não pode alterar esses tipos para implementar novas interfaces.
public static T Get<T>(string val)
{
return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromInvariantString(val);
}