Метод String.Contains выглядит так внутренне
public bool Contains(string value)
{
return this.IndexOf(value, StringComparison.Ordinal) >= 0;
}
Перегрузка IndexOf
, которая вызывается, выглядит так:
public int IndexOf(string value, StringComparison comparisonType)
{
return this.IndexOf(value, 0, this.Length, comparisonType);
}
Здесь выполняется другой вызов окончательной перегрузки, который затем вызывает соответствующий метод CompareInfo.IndexOf
с сигнатурой
public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
Следовательно, вызов последней перегрузки был бы самым быстрым (хотя в большинстве случаев он может считаться микро-оптимизацией).
Возможно, мне не хватает чего-то очевидного, но почему метод Contains
не вызывает окончательную перегрузку напрямую, учитывая, что в промежуточном вызове не делается никакой другой работы и что на обеих этапах доступна одна и та же информация?
Единственное преимущество, заключающееся в том, что если подпись окончательной перегрузки изменяется, необходимо сделать только одно изменение (промежуточного метода) или больше для дизайна, чем это?
Изменить комментарии (см. обновление 2 для пояснения скорости)
Чтобы уточнить различия в производительности, которые я получаю, если я где-то ошибся:
Я провел этот тест (зациклился 5 раз, чтобы избежать смещения дрожания) и использовал этот метод расширения для сравнения с методом String.Contains
public static bool QuickContains(this string input, string value)
{
return input.IndexOf(value, 0, input.Length, StringComparison.OrdinalIgnoreCase) >= 0;
}
с петлей, выглядящей так:
for (int i = 0; i < 1000000; i++)
{
bool containsStringRegEx = testString.QuickContains("STRING");
}
sw.Stop();
Console.WriteLine("QuickContains: " + sw.ElapsedMilliseconds);
В тестовом тесте QuickContains
кажется на 50% быстрее, чем String.Contains
на моей машине.
Обновление 2 (объясняется разница в производительности)
Я заметил что-то несправедливое в тесте, который объясняет многое. Сам тест состоял в том, чтобы измерять строки без учета регистра, но поскольку String.Contains
может выполнять только поисковые запросы с учетом регистра, был включен метод ToUpper
. Это исказило бы результаты не в терминах конечного результата, а по крайней мере в плане простого измерения производительности String.Contains
при нечувствительных к регистру поисках.
Итак, если я использую этот метод расширения
public static bool QuickContains(this string input, string value)
{
return input.IndexOf(value, 0, input.Length, StringComparison.Ordinal) >= 0;
}
используйте StringComparison.Ordinal
в вызове 2 перегрузки IndexOf
и удалите ToUpper
, метод QuickContains
фактически станет самым медленным. IndexOf
и Contains
в значительной степени соответствуют производительности. Так ясно, что вызов ToUpper
исказил результаты того, почему было такое несоответствие между Contains
и IndexOf
.
Не знаю, почему метод расширения QuickContains
стал самым медленным. (Возможно, связано с тем, что Contains
имеет атрибут [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
?).
Вопрос по-прежнему заключается в том, почему метод 4 перегрузки не вызывается напрямую, но кажется, что производительность не влияет (как указал Адриан и Делнан в комментариях) решением.