. «Чистая» сборки может удалить «мертвую древесину», которая может быть оставлена лежащей рядом с предыдущими сборками, неудачными сборками, неполными сборками и другими проблемами сборки.
В общем случае среда IDE или сборка будет включать в себя некоторую форму «чистой» функции, но это может быть неправильно настроено (например, в ручном файле) или может завершиться неудачей (например, промежуточные или результирующие двоичные файлы - только).
После завершения «очистки» убедитесь, что «чистый» преуспел, и весь сгенерированный промежуточный файл (например, автоматический файл makefile) был успешно удален.
Этот процесс можно рассматривать как конечный вариант, но часто является хорошим первым шагом ; особенно если недавно был добавлен код, связанный с ошибкой (локально или из исходного репозитория).
Я просто высмеивал ваш код с помощью этого
foreach(var v in Enumerable.Range(1,10).Skip(1))
v.Dump();
И здесь генерируется IL.
IL_0001: nop
IL_0002: ldc.i4.1
IL_0003: ldc.i4.s 0A
IL_0005: call System.Linq.Enumerable.Range
IL_000A: ldc.i4.1
IL_000B: call System.Linq.Enumerable.Skip//Call to Skip
IL_0010: callvirt System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator
IL_0015: stloc.1 // CS$5$0000
IL_0016: br.s IL_0026
IL_0018: ldloc.1 // CS$5$0000
IL_0019: callvirt System.Collections.Generic.IEnumerator<System.Int32>.get_Current
IL_001E: stloc.0 // v
IL_001F: ldloc.0 // v
IL_0020: call LINQPad.Extensions.Dump
IL_0025: pop
IL_0026: ldloc.1 // CS$5$0000
IL_0027: callvirt System.Collections.IEnumerator.MoveNext
IL_002C: stloc.2 // CS$4$0001
IL_002D: ldloc.2 // CS$4$0001
IL_002E: brtrue.s IL_0018
IL_0030: leave.s IL_0042
IL_0032: ldloc.1 // CS$5$0000
IL_0033: ldnull
IL_0034: ceq
IL_0036: stloc.2 // CS$4$0001
IL_0037: ldloc.2 // CS$4$0001
IL_0038: brtrue.s IL_0041
IL_003A: ldloc.1 // CS$5$0000
IL_003B: callvirt System.IDisposable.Dispose
IL_0040: nop
IL_0041: endfinally
Как вы можете видеть, Skip
вызывается только один раз.
Эквивалентный код c # будет выглядеть примерно так:
IEnumerator<int> e = ((IEnumerable<int>)values).GetEnumerator();//Get the enumerator
try
{
int m;//This variable is here prior to c#5.0
while(e.MoveNext())
{//int m; is declared here starting from c#5.0
m = (int)(int)e.Current;
//Your code here
}
}
finally
{
if (e != null) ((IDisposable)e).Dispose();
}
. Рассмотрим приведенный ниже код. Если для каждой итерации используются следующие вызовы VeryLongRunningMethodThatReturnsEnumerable
, то это будет кошмар. Огромная ошибка в дизайне языка. К счастью, этого не делает.
foreach(var obj in VeryLongRunningMethodThatReturnsEnumerable())
{
//Do something with that obj
}
Какова ваша итерация - результат команды:
myCollection.Skip(1)
Это эффективно возвращает IEnumerable
типа myCollection
, который пропустил первый элемент. Поэтому ваш foreach тогда против нового IEnumerable
, которому не хватает первого элемента. foreach
заставляет фактическую оценку полученного метода Skip(int)
посредством перечисления (его выполнение откладывается до перечисления, как и другие методы LINQ, такие как Where
и т. Д.). Это будет то же самое, что:
var mySkippedCollection = myCollection.Skip(1);
foreach (object i in mySkippedCollection)
...
Вот код, который Skip(int)
действительно заканчивает выполнение:
private static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (count > 0 && enumerator.MoveNext())
{
count--;
}
if (count <= 0)
{
while (enumerator.MoveNext())
{
yield return enumerator.Current; // <-- here's your lazy eval
}
}
}
yield break;
}
Извлеките его, и он, вероятно, станет более ясным.
var myCollection = new List<object>();
var skipped = myCollection.Skip(1);
foreach (var i in skipped) {
Console.WriteLine(i.ToString());
}
Так что пропущено только IEnumerable
, которое теперь перечисляет foreach
.
Вот что выглядит IL как и в этом случае:
IL_0000: newobj System.Collections.Generic.List<System.Object>..ctor
IL_0005: stloc.0 // myCollection
IL_0006: ldloc.0 // myCollection
IL_0007: ldc.i4.1
IL_0008: call System.Linq.Enumerable.Skip
IL_000D: stloc.1 // skipped
IL_000E: ldloc.1 // skipped
IL_000F: callvirt System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator
IL_0014: stloc.3 // CS$5$0000
IL_0015: br.s IL_0029
IL_0017: ldloc.3 // CS$5$0000
IL_0018: callvirt System.Collections.Generic.IEnumerator<System.Object>.get_Current
IL_001D: stloc.2 // i
IL_001E: ldloc.2 // i
IL_001F: callvirt System.Object.ToString
IL_0024: call System.Console.WriteLine
IL_0029: ldloc.3 // CS$5$0000
IL_002A: callvirt System.Collections.IEnumerator.MoveNext
IL_002F: brtrue.s IL_0017
IL_0031: leave.s IL_003D
IL_0033: ldloc.3 // CS$5$0000
IL_0034: brfalse.s IL_003C
IL_0036: ldloc.3 // CS$5$0000
IL_0037: callvirt System.IDisposable.Dispose
IL_003C: endfinally
IL для вашего кода выглядит схоже:
var myCollection = new List<object>();
foreach (var i in myCollection.Skip(1)) {
Console.WriteLine(i.ToString());
}
IL_0000: newobj System.Collections.Generic.List<System.Object>..ctor
IL_0005: stloc.0 // myCollection
IL_0006: ldloc.0 // myCollection
IL_0007: ldc.i4.1
IL_0008: call System.Linq.Enumerable.Skip <-- 1 Call to .Skip() outside the loop.
IL_000D: callvirt System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator
IL_0012: stloc.2 // CS$5$0000
IL_0013: br.s IL_0027
IL_0015: ldloc.2 // CS$5$0000
IL_0016: callvirt System.Collections.Generic.IEnumerator<System.Object>.get_Current
IL_001B: stloc.1 // i
IL_001C: ldloc.1 // i
IL_001D: callvirt System.Object.ToString
IL_0022: call System.Console.WriteLine
IL_0027: ldloc.2 // CS$5$0000
IL_0028: callvirt System.Collections.IEnumerator.MoveNext
IL_002D: brtrue.s IL_0015
IL_002F: leave.s IL_003B
IL_0031: ldloc.2 // CS$5$0000
IL_0032: brfalse.s IL_003A
IL_0034: ldloc.2 // CS$5$0000
IL_0035: callvirt System.IDisposable.Dispose
IL_003A: endfinally
У него все еще есть один вызов .Skip ().
Все выражение с Skip
будет вызвано только один раз. Skip
использует отложенное выполнение так, чтобы оно выполнялось после того, как есть действия, которые не используют отложенное выполнение. В этот момент дерево выражений построено на фоне, и ссылка на экземпляр IEnumerable
возвращается обратно вызывающему, который использует его, если ничего не изменяется.
Вы должны понимать, как работает foreach
. Этот цикл foreach:
foreach(T t in GetSomeEnumerable())
DoSomethingWithT(t);
эквивалентен этому коду:
var e = GetSomeEnumerable().GetEnumerator();
try{
while(e.MoveNext()){
T t = (T)e.Current; // unless e is the generic IEnumerator<T>,
// in which case, there is no cast
DoSomethingWithT(t);
}
}finally{
if(e is IDisposable)
e.Dispose();
}