Изменить 02 февраля 2010 :
На мой взгляд, есть по крайней мере два способа интерпретации этого вопроса.
Почему метод расширения Select<T,
TResult>
, когда
вызывается на экземпляр класса, который
реализует ICollection<T>
, а не
вернуть объект, который обеспечивает
Count
собственность; и почему
Count<T>
метод расширения не
проверить это свойство, чтобы
обеспечить O (1) производительность, когда два
методы связаны?
Эта версия вопроса не содержит ложных предположений о том, как работают расширения Linq, и является допустимым вопросом, поскольку вызов ICollection<T>.Select.Count
, в конце концов, всегда будет возвращать то же значение, что и ICollection<T>.Count
. Вот как Мерадад истолковал вопрос, на который он дал исчерпывающий ответ.
Но Я прочитал вопрос как спрашивающий ...
Если метод расширения Count<T>
обеспечивает O (1)
производительность для объекта класса
реализации ICollection<T>
, почему
это обеспечивает O (N) производительность для
возвращаемое значение
Select<T, TResult>
метод расширения?
В этой версии вопроса есть ошибочное предположение: методы расширения Linq работают вместе, собирая маленькие коллекции один за другим (в памяти) и выставляя их через интерфейс IEnumerable<T>
.
Если бы так работали расширения Linq, метод Select
мог бы выглядеть примерно так:
public static IEnumerable<TResult> Select<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector) {
List<TResult> results = new List<TResult>();
foreach (T input in source)
results.Add(selector(input));
return results;
}
Более того, если бы это было реализацией Select
, я думаю, вы бы нашли, что большая часть кода, использующего этот метод, будет вести себя точно так же. Но это было бы расточительно и фактически вызывало бы исключения в некоторых случаях, подобных тому, который я описал в своем первоначальном ответе.
На самом деле, я считаю, что реализация метода Select
намного ближе к чему-то вроде этого:
public static IEnumerable<TResult> Select<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector) {
foreach (T input in source)
yield return selector(input);
yield break;
}
Это обеспечивает ленивую оценку и объясняет, почему свойство Count
недоступно в O (1) времени для метода Count
.
Другими словами, в то время как Мерадад ответил на вопрос почему Select
не был разработан по-другому, так что Select.Count
будет вести себя по-другому , я предложил свой лучший ответ на вопрос почему Select.Count
ведет себя так, как .
ОРИГИНАЛЬНЫЙ ОТВЕТ :
Метод побочных эффектов не является ответом.
Согласно ответу Мехрдада:
Неважно, что
результат выбора лениво оценивается.
Я не покупаю это. Позвольте мне объяснить, почему.
Для начала рассмотрим следующие два очень похожих метода:
public static IEnumerable<double> GetRandomsAsEnumerable(int N) {
Random r = new Random();
for (int i = 0; i < N; ++i)
yield return r.NextDouble();
yield break;
}
public static double[] GetRandomsAsArray(int N) {
Random r = new Random();
double[] values = new double[N];
for (int i = 0; i < N; ++i)
values[i] = r.NextDouble();
return values;
}
ОК, что делают эти методы? Каждый из них возвращает столько случайных двойных чисел, сколько пожелает пользователь (до int.MaxValue
). Имеет ли значение ленивый метод оценки или нет? Чтобы ответить на этот вопрос, давайте взглянем на следующий код:
public static double Invert(double value) {
return 1.0 / value;
}
public static void Test() {
int a = GetRandomsAsEnumerable(int.MaxValue).Select(Invert).Count();
int b = GetRandomsAsArray(int.MaxValue).Select(Invert).Count();
}
Можете ли вы догадаться, что произойдет с этими двумя вызовами методов? Позвольте мне избавить вас от необходимости скопировать этот код и протестировать его самостоятельно:
Первая переменная , a
, (после потенциально значительного промежутка времени) будет инициализирована значением int.MaxValue
(в настоящее время 2147483647). секунда один, b
, очень вероятно будет прервана OutOfMemoryException
.
Поскольку Select
и другие методы расширения Linq оцениваются лениво, они позволяют вам делать то, что вы просто не могли бы сделать иначе. Выше приведен довольно тривиальный пример. Но мое главное - оспорить утверждение, что ленивая оценка не важна. Утверждение Мехрдада о том, что свойство Count
«действительно известно с самого начала и может быть оптимизировано», фактически ставит вопрос. Проблема может показаться простой для метода Select
, но Select
не является особенной; он возвращает IEnumerable<T>
точно так же, как и остальные методы расширения Linq, и для того, чтобы эти методы «знали» Count
их возвращаемых значений , потребовалось бы кэширование полных коллекций и, следовательно, запретить ленивую оценку .
Ленивая оценка - это ответ.
По этой причине я должен согласиться с одним из первоначальных респондентов (чей ответ теперь, кажется, исчез), что ленивая оценка действительно является ответом здесь. Идея о том, что необходимо учитывать побочные эффекты метода, на самом деле вторична, поскольку в любом случае это уже является побочным продуктом ленивой оценки.
Постскриптум: Я сделал очень напористые заявления и подчеркнул свои аргументы главным образом потому, что я хотел уточнить, в чем заключается мой аргумент, а не из-за неуважения к любым другим ответам, включая ответы Мехрдада, которые я считаю проницательными но не попадает в цель.