TLDR:
Viele Antworten mit Behauptungen über Leistung und schlechte Praxis, deshalb kläre ich das hier.
Die Ausnahmeroute ist schneller für eine höhere Anzahl zurückgegebener Spalten, die Schleifenroute ist schneller für eine niedrigere Anzahl von Spalten und der Überkreuzungspunkt liegt bei etwa 11 Spalten. Scrollen Sie nach unten, um ein Diagramm und einen Testcode anzuzeigen.
Vollständige Antwort:
Der Code für einige der Top-Antworten funktioniert, aber es gibt hier eine grundlegende Debatte für die "bessere" Antwort, die auf der Akzeptanz der Ausnahmebehandlung in der Logik und der damit verbundenen Leistung basiert.
Um das zu klären, glaube ich nicht, dass es viele Hinweise zu CATCHING-Ausnahmen gibt. Microsoft hat einige Anleitungen zum Werfen von Ausnahmen. Dort heißt es:
Verwenden Sie nach Möglichkeit KEINE Ausnahmen für den normalen Kontrollfluss.
Die erste Anmerkung ist die Nachsicht von "wenn möglich". Noch wichtiger ist, dass die Beschreibung diesen Kontext angibt:
framework designers should design APIs so users can write code that does not throw exceptions
Dies bedeutet, dass Sie beim Schreiben einer API, die möglicherweise von einer anderen Person verwendet wird, die Möglichkeit erhalten, eine Ausnahme ohne Versuch / Fang zu navigieren. Stellen Sie beispielsweise TryParse mit Ihrer ausnahmeauslösenden Parse-Methode bereit. Nirgendwo heißt es jedoch, dass Sie keine Ausnahme abfangen sollten.
Wie ein anderer Benutzer betont, haben Fänge außerdem immer das Filtern nach Typ und in jüngster Zeit das weitere Filtern über die when-Klausel ermöglicht . Dies scheint eine Verschwendung von Sprachfunktionen zu sein, wenn wir sie nicht verwenden sollen.
Es kann gesagt werden, dass es einige Kosten für eine ausgelöste Ausnahme gibt, und diese Kosten können die Leistung in einer schweren Schleife beeinflussen. Es kann jedoch auch gesagt werden, dass die Kosten einer Ausnahme in einer "verbundenen Anwendung" vernachlässigbar sein werden. Die tatsächlichen Kosten wurden vor über einem Jahrzehnt untersucht: https://stackoverflow.com/a/891230/852208
Mit anderen Worten, die Kosten für eine Verbindung und Abfrage einer Datenbank werden wahrscheinlich die Kosten einer ausgelösten Ausnahme in den Schatten stellen.
Abgesehen davon wollte ich herausfinden, welche Methode wirklich schneller ist. Wie erwartet gibt es keine konkrete Antwort.
Jeder Code, der die Spalten durchläuft, wird langsamer, wenn die Anzahl der Spalten vorhanden ist. Es kann auch gesagt werden, dass jeder Code, der auf Ausnahmen beruht, abhängig von der Rate, in der die Abfrage nicht gefunden wird, langsamer wird.
Ich nahm die Antworten von Chad Grant und Matt Hamilton und führte beide Methoden mit bis zu 20 Spalten und einer Fehlerrate von bis zu 50% aus (das OP gab an, dass er diese beiden Tests zwischen verschiedenen Prozessen verwendete, also nahm ich nur zwei an). .
Hier sind die mit LinqPad gezeichneten Ergebnisse:
Die Zickzacklinien hier sind Fehlerraten (Spalte nicht gefunden) innerhalb jeder Spaltenanzahl.
Bei engeren Ergebnismengen ist das Schleifen eine gute Wahl. Die GetOrdinal / Exception-Methode reagiert jedoch bei weitem nicht so empfindlich auf die Anzahl der Spalten und beginnt, die Schleifenmethode um 11 Spalten herum zu übertreffen.
Das heißt, ich habe keine Präferenz für die Leistung, da 11 Spalten als durchschnittliche Anzahl von Spalten, die über eine gesamte Anwendung zurückgegeben werden, vernünftig klingen. In beiden Fällen handelt es sich hier um Bruchteile einer Millisekunde.
Unter dem Aspekt der Code-Einfachheit und der Alias-Unterstützung würde ich mich wahrscheinlich für die GetOrdinal-Route entscheiden.
Hier ist der Test in Linqpad-Form. Fühlen Sie sich frei, mit Ihrer eigenen Methode neu zu posten:
void Main()
{
var loopResults = new List<Results>();
var exceptionResults = new List<Results>();
var totalRuns = 10000;
for (var colCount = 1; colCount < 20; colCount++)
{
using (var conn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=master;Integrated Security=True;"))
{
conn.Open();
//create a dummy table where we can control the total columns
var columns = String.Join(",",
(new int[colCount]).Select((item, i) => $"'{i}' as col{i}")
);
var sql = $"select {columns} into #dummyTable";
var cmd = new SqlCommand(sql,conn);
cmd.ExecuteNonQuery();
var cmd2 = new SqlCommand("select * from #dummyTable", conn);
var reader = cmd2.ExecuteReader();
reader.Read();
Func<Func<IDataRecord, String, Boolean>, List<Results>> test = funcToTest =>
{
var results = new List<Results>();
Random r = new Random();
for (var faultRate = 0.1; faultRate <= 0.5; faultRate += 0.1)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var faultCount=0;
for (var testRun = 0; testRun < totalRuns; testRun++)
{
if (r.NextDouble() <= faultRate)
{
faultCount++;
if(funcToTest(reader, "colDNE"))
throw new ApplicationException("Should have thrown false");
}
else
{
for (var col = 0; col < colCount; col++)
{
if(!funcToTest(reader, $"col{col}"))
throw new ApplicationException("Should have thrown true");
}
}
}
stopwatch.Stop();
results.Add(new UserQuery.Results{
ColumnCount = colCount,
TargetNotFoundRate = faultRate,
NotFoundRate = faultCount * 1.0f / totalRuns,
TotalTime=stopwatch.Elapsed
});
}
return results;
};
loopResults.AddRange(test(HasColumnLoop));
exceptionResults.AddRange(test(HasColumnException));
}
}
"Loop".Dump();
loopResults.Dump();
"Exception".Dump();
exceptionResults.Dump();
var combinedResults = loopResults.Join(exceptionResults,l => l.ResultKey, e=> e.ResultKey, (l, e) => new{ResultKey = l.ResultKey, LoopResult=l.TotalTime, ExceptionResult=e.TotalTime});
combinedResults.Dump();
combinedResults
.Chart(r => r.ResultKey, r => r.LoopResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.AddYSeries(r => r.ExceptionResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.Dump();
}
public static bool HasColumnLoop(IDataRecord dr, string columnName)
{
for (int i = 0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
public static bool HasColumnException(IDataRecord r, string columnName)
{
try
{
return r.GetOrdinal(columnName) >= 0;
}
catch (IndexOutOfRangeException)
{
return false;
}
}
public class Results
{
public double NotFoundRate { get; set; }
public double TargetNotFoundRate { get; set; }
public int ColumnCount { get; set; }
public double ResultKey {get => ColumnCount + TargetNotFoundRate;}
public TimeSpan TotalTime { get; set; }
}