TLDR:
Множество ответов с претензиями по поводу производительности и плохой практики, поэтому поясняю это здесь.
Маршрут исключения является более быстрым для большего числа возвращаемых столбцов, маршрут петли быстрее для меньшего числа столбцов, а точка пересечения составляет около 11 столбцов. Прокрутите вниз, чтобы увидеть график и тестовый код.
Полный ответ:
Код для некоторых лучших ответов работает, но здесь есть основополагающая дискуссия о «лучшем» ответе, основанном на принятии обработки логики исключений и связанной с этим производительности.
Чтобы уяснить это, я не верю, что есть много рекомендаций относительно исключений CATCHING. У Microsoft есть некоторые рекомендации относительно БРОНИРОВАНИЯ исключений. Там они заявляют:
НЕ используйте исключения для нормального потока управления, если это возможно.
Первое примечание - снисходительность «если возможно». Что еще более важно, описание дает этот контекст:
framework designers should design APIs so users can write code that does not throw exceptions
Это означает, что если вы пишете API, который может использоваться кем-то другим, дайте ему возможность перемещаться по исключению без попытки / улова. Например, предоставьте TryParse свой метод Parse, генерирующий исключения. Однако нигде это не говорит о том, что вы не должны ловить исключение.
Кроме того, как указывает другой пользователь, уловы всегда допускают фильтрацию по типу и в последнее время позволяют дополнительную фильтрацию с помощью предложения when . Это кажется пустой тратой языковых возможностей, если мы не должны их использовать.
Можно сказать, что за выброшенное исключение есть НЕКОТОРЫЕ затраты, и они МОГУТ влиять на производительность в тяжелой петле. Однако можно также сказать, что стоимость исключения будет незначительной в «связанном приложении». Фактическая стоимость была исследована более десяти лет назад: https://stackoverflow.com/a/891230/852208
Другими словами, стоимость соединения и запроса к базе данных, вероятно, будет меньше, чем стоимость брошенного исключения.
Помимо всего прочего, я хотел определить, какой метод действительно быстрее. Как и следовало ожидать, нет конкретного ответа.
Любой код, который зацикливается на столбцах, становится медленнее, поскольку существует количество столбцов. Можно также сказать, что любой код, использующий исключения, будет работать медленно в зависимости от скорости, с которой не удается найти запрос.
Взяв ответы Чеда Гранта и Мэтта Гамильтона, я запустил оба метода с количеством столбцов до 20 и частотой ошибок до 50% (ОП указал, что он использовал этот два теста между разными процессами, поэтому я предположил, что их всего два) ,
Вот результаты, полученные с помощью LinqPad:
Зигзаги здесь - это частота отказов (столбец не найден) в пределах каждого столбца.
Более узкие результирующие наборы - это хороший выбор. Тем не менее, метод GetOrdinal / Exception не так чувствителен к количеству столбцов и начинает опережать метод зацикливания около 11 столбцов.
Тем не менее, у меня на самом деле нет предпочтений в отношении производительности, поскольку 11 столбцов звучат разумно, так как среднее количество столбцов возвращается во всем приложении. В любом случае мы говорим о долях миллисекунды здесь.
Однако, с точки зрения простоты кода и поддержки псевдонимов, я бы, вероятно, пошел по маршруту GetOrdinal.
Вот тест в форме linqpad. Не стесняйтесь делать репосты собственным методом:
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; }
}