Frage Schnellste Möglichkeit, um zu prüfen, ob eine Zeichenfolge analysiert werden kann


Ich analysiere CSV-Dateien zu Listen von Objekten mit stark typisierten Eigenschaften. Dies beinhaltet die Analyse jedes String-Wertes von der Datei zu einem IConvertible Art (int, decimal, double, DateTimeusw.) mit TypeDescriptor.

Ich benutze ein try catch um Situationen zu behandeln, in denen das Parsen fehlschlägt. Die genauen Details, wo und warum diese Ausnahme auftritt, werden dann für weitere Untersuchungen protokolliert. Unten ist der eigentliche Parsing-Code:

try
{
    parsedValue = TypeDescriptor.GetConverter(type).ConvertFromString(dataValue);
}
catch (Exception ex)
{
    // Log failure
}

Problem:

Wenn Werte erfolgreich analysiert werden, ist der Prozess schnell. Wenn Daten mit vielen ungültigen Daten analysiert werden, kann der Prozess tausende Male langsamer sein (aufgrund des Einfangens der Ausnahme).

Ich habe das mit Parsing getestet DateTime. Dies sind die Leistungszahlen:

  • Erfolgreiches Parsen: Durchschnitt von 32 Ticks pro Parse
  • Parsing fehlgeschlagen: Durchschnitt von 146296 Ticks pro Parse

Das ist mehr als 4500 mal langsamer.

Frage:

Ist es möglich für mich zu überprüfen, ob ein String-Wert erfolgreich geparst werden kann, ohne dass ich meinen teuren verwenden muss? try catch Methode? Oder vielleicht gibt es einen anderen Weg, ich sollte das tun?

EDIT: Ich muss verwenden TypeDescriptor (und nicht DateTime.TryParse) weil der Typ zur Laufzeit ermittelt wird.


18
2018-05-30 12:09


Ursprung


Antworten:


Wenn Sie eine bekannte Gruppe von zu konvertierenden Typen haben, können Sie eine Reihe von Typen erstellen if/elseif/elseif/else (oder switch/case über den Typnamen), um sie im Wesentlichen auf spezialisierte Analysemethoden zu verteilen. Das sollte ziemlich schnell sein. Dies ist wie in beschrieben @ Fabios Antwort.

Wenn Sie weiterhin Leistungsprobleme haben, können Sie auch eine Nachschlagetabelle erstellen, mit der Sie neue Analysemethoden hinzufügen können, wenn Sie sie unterstützen müssen:

Gegeben einige grundlegende Parsing-Wrapper:

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

Sie können dann einen Konvertierungshelfer einrichten, der die Suche durchführt und den entsprechenden Parser aufruft:

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

Dann könnte die Nutzung wie folgt aussehen:

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

Dies könnte wahrscheinlich die Generika erweitert haben, um zu vermeiden object Boxen und Typcasting, aber wie es aussieht, funktioniert das gut; vielleicht nur diesen Aspekt optimieren, wenn Sie daraus eine messbare Leistung haben.

EDIT: Sie können die aktualisieren DataConversion.Convert Methode, so dass, wenn es nicht den angegebenen Konverter registriert hat, kann es auf Ihre fallen TypeConverter Methode oder werfen Sie eine entsprechende Ausnahme. Es liegt an Ihnen, ob Sie ein Catch-All haben möchten oder einfach nur Ihre vordefinierten unterstützten Typen haben und vermeiden möchten try/catch noch einmal. Wie es aussieht, wurde der Code aktualisiert, um ein zu werfen NotSupportedException mit einer Nachricht, die den nicht unterstützten Typ angibt. Fühlen Sie sich frei zu optimieren, wie es sinnvoll ist. Im Hinblick auf die Leistung ist es vielleicht sinnvoll, den Catch-All zu verwenden, da diese vielleicht weniger und weiter voneinander entfernt sein werden, wenn Sie spezialisierte Parser für die am häufigsten verwendeten Typen angeben.


13
2018-05-30 12:50



Wenn Sie einen Typ kennen, den Sie analysieren möchten, verwenden Sie die TryParse-Methode:

String value;
Int32 parsedValue;
if (Int32.TryParse(value, parsedValue) == True)
    // actions if parsed ok
else
    // actions if not parsed

Gleiches gilt für andere Typen

Decimal.TryParse(value, parsedValue)
Double.TryParse(value, parsedValue)
DateTime.TryParse(value, parsedValue)

Oder Sie können die nächste Problemumgehung verwenden:

Erstellen Sie eine Parse-Methode für jeden Typ mit demselben Namen, aber unterschiedlicher Signatur (wrap TryParse innerhalb von ihnen):

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

Dann können Sie Methode verwenden TryParsing mit deinen Typen


4
2018-05-30 12:15



Wie wäre es, vor dem Aufruf von Parse einen regulären Ausdruck für jeden Typ zu erstellen und ihn auf den String anzuwenden? Sie müssten den regulären Ausdruck so erstellen, dass die Analyse nicht funktioniert, wenn die Zeichenfolge nicht übereinstimmt. Dies wäre ein wenig langsamer, wenn die Zeichenfolge analysiert, da Sie den Regex-Test durchführen müssten, aber es wäre viel schneller, wenn es nicht analysiert.

Sie könnten die Regex-Zeichenfolgen in a eingeben Dictionary<Type, string>, die bestimmen würde, welche Regex-Zeichenfolge einfach zu verwenden ist.


2
2018-05-30 12:48



Du könntest das benutzen TryParse Methode :

if (DateTime.TryParse(input, out dateTime))
{
    Console.WriteLine(dateTime);
}

2
2018-05-30 12:11



Es kommt darauf an. Wenn Sie eine DateTime verwenden, können Sie immer die TryParse Funktion. Dies wird eine Größenordnung schneller sein.


1
2018-05-30 12:11