returns - remarks c#
La manera más rápida de verificar si una cadena puede ser analizada (5)
Estoy analizando archivos CSV a listas de objetos con propiedades fuertemente tipadas. Esto implica analizar cada valor de cadena del archivo a un tipo IConvertible
( int
, decimal
, double
, DateTime
, etc.) usando TypeDescriptor
.
Estoy usando un try catch
para manejar situaciones cuando falla el análisis. Los detalles exactos de dónde y por qué ocurre esta excepción se registran para una mayor investigación. A continuación se muestra el código de análisis en realidad:
try
{
parsedValue = TypeDescriptor.GetConverter(type).ConvertFromString(dataValue);
}
catch (Exception ex)
{
// Log failure
}
Problema:
Cuando los valores se analizan correctamente, el proceso es rápido. Cuando se analizan datos con muchos datos no válidos, el proceso puede tardar miles de veces más lento (debido a la captura de la excepción).
He estado probando esto con el análisis de DateTime
. Estas son las cifras de rendimiento:
- Análisis exitoso: promedio de 32 tics por análisis
- Error al analizar: promedio de 146296 tics por análisis
Eso es más de 4500 veces más lento.
Pregunta:
¿Puedo ver si un valor de cadena se puede analizar correctamente sin tener que usar mi costoso método de try catch
? O tal vez hay otra forma en que debería estar haciendo esto?
EDITAR: Necesito usar TypeDescriptor
(y no DateTime.TryParse
) porque el tipo se determina en tiempo de ejecución.
¿Qué tal construir una expresión regular para cada tipo y aplicarla a la cadena antes de llamar a Parse? Tendría que compilar la expresión regular de modo que si la cadena no coincide, no se analizará. Esto sería un poco más lento si la cadena se analiza, ya que tendrías que hacer la prueba de expresiones regulares, pero sería mucho más rápido si no se analiza.
Podría poner las cadenas de expresiones regulares en un Dictionary<Type, string>
, lo que haría que la determinación de qué cadena de expresiones regulares se usara de manera simple.
Depende. Si está utilizando un DateTime, siempre puede usar la función TryParse . Esta será una magnitud más rápida.
Podrías usar el método TryParse
:
if (DateTime.TryParse(input, out dateTime))
{
Console.WriteLine(dateTime);
}
Si conoce un tipo en el que intenta analizar, use el método TryParse:
String value;
Int32 parsedValue;
if (Int32.TryParse(value, parsedValue) == True)
// actions if parsed ok
else
// actions if not parsed
Igual para otros tipos
Decimal.TryParse(value, parsedValue)
Double.TryParse(value, parsedValue)
DateTime.TryParse(value, parsedValue)
O puedes usar la siguiente solución:
Cree métodos de análisis para cada tipo con el mismo nombre, pero con una firma diferente (incluya dentro de ellos TryParse):
Private bool TryParsing(String value, out Int32 parsedValue)
{
Return Int32.TryParse(value, parsedValue)
}
Private bool TryParsing(String value, out Double parsedValue)
{
Return Double.TryParse(value, parsedValue)
}
Private bool TryParsing(String value, out Decimal parsedValue)
{
Return Decimal.TryParse(value, parsedValue)
}
Private bool TryParsing(String value, out DateTime parsedValue)
{
Return DateTime.TryParse(value, parsedValue)
}
Luego puedes usar el método TryParsing
con tus tipos
Si tiene un conjunto conocido de tipos para convertir, puede hacer una serie de if/elseif/elseif/else
(o switch/case
en el nombre del tipo) para distribuirlo esencialmente a métodos de análisis especializados. Esto debería ser bastante rápido. Esto es como se describe en la respuesta de @Fabio .
Si aún tiene problemas de rendimiento, también puede crear una tabla de búsqueda que le permitirá agregar nuevos métodos de análisis, según sea necesario:
Dados algunos envoltorios de análisis básicos:
public delegate bool TryParseMethod<T>(string input, out T value);
public interface ITryParser
{
bool TryParse(string input, out object value);
}
public class TryParser<T> : ITryParser
{
private TryParseMethod<T> ParsingMethod;
public TryParser(TryParseMethod<T> parsingMethod)
{
this.ParsingMethod = parsingMethod;
}
public bool TryParse(string input, out object value)
{
T parsedOutput;
bool success = ParsingMethod(input, out parsedOutput);
value = parsedOutput;
return success;
}
}
Luego puede configurar un ayudante de conversión que realice la búsqueda y llame al analizador apropiado:
public static class DataConversion
{
private static Dictionary<Type, ITryParser> Parsers;
static DataConversion()
{
Parsers = new Dictionary<Type, ITryParser>();
AddParser<DateTime>(DateTime.TryParse);
AddParser<int>(Int32.TryParse);
AddParser<double>(Double.TryParse);
AddParser<decimal>(Decimal.TryParse);
AddParser<string>((string input, out string value) => {value = input; return true;});
}
public static void AddParser<T>(TryParseMethod<T> parseMethod)
{
Parsers.Add(typeof(T), new TryParser<T>(parseMethod));
}
public static bool Convert<T>(string input, out T value)
{
object parseResult;
bool success = Convert(typeof(T), input, out parseResult);
if (success)
value = (T)parseResult;
else
value = default(T);
return success;
}
public static bool Convert(Type type, string input, out object value)
{
ITryParser parser;
if (Parsers.TryGetValue(type, out parser))
return parser.TryParse(input, out value);
else
throw new NotSupportedException(String.Format("The specified type /"{0}/" is not supported.", type.FullName));
}
}
Entonces el uso podría ser como:
//for a known type at compile time
int value;
if (!DataConversion.Convert<int>("3", out value))
{
//log failure
}
//or for unknown type at compile time:
object value;
if (!DataConversion.Convert(myType, dataValue, out value))
{
//log failure
}
Probablemente esto podría haber ampliado los genéricos para evitar el boxeo de object
y la conversión de tipos, pero tal como está, funciona bien; Tal vez solo optimice ese aspecto si tiene un rendimiento medible de él.
EDITAR: puede actualizar el método DataConversion.Convert
para que, si no tiene registrado el convertidor especificado, puede recurrir a su método TypeConverter
o lanzar una excepción apropiada. Depende de usted si desea tener un catch-all o simplemente tener su conjunto predefinido de tipos compatibles y evitar tener su try/catch
nuevamente. En su estado actual, el código se ha actualizado para lanzar una NotSupportedException
con un mensaje que indica el tipo no compatible. Siéntase libre de modificar como tiene sentido. En lo que respecta al rendimiento, quizás tenga sentido hacer el catch-all, ya que tal vez sean menos y muy separados una vez que especifique analizadores especializados para los tipos más utilizados.