Давайте разберем это по частям, пока не поймем.Доверьтесь мне;не торопитесь и прочитайте это, и это станет для вас откровением в понимании Enumerable
типов и ответе на ваш вопрос.
Посмотрите на интерфейс IEnumerable
, который является основой IEnumerable<T>
.Он содержит один метод;IEnumerator GetEnumerator();
.
Перечислители - хитрое чудовище, потому что они могут делать все, что хотят.Все, что действительно имеет значение, - это вызов GetEnumerator()
, который происходит автоматически в цикле foreach
;или вы можете сделать это вручную.
Что делает GetEnumerator()
?Он возвращает другой интерфейс, IEnumerator
.
Это магия.IEnumerator
имеет 1 свойство и 2 метода.
object Current { get; }
bool MoveNext();
void Reset();
Давайте разберем магию.
Сначала позвольте мне объяснить, каковы они, как правило, и я говорю обычно, потому что, как я уже упоминалможет быть хитрый зверь.Вам разрешено реализовывать это, однако вы выбираете ... Некоторые типы не соответствуют стандартам.
object Current { get; }
очевидно.Получает текущий объект в IEnumerator
;по умолчанию это может быть ноль.
bool MoveNext();
Возвращает true
, если в IEnumerator
есть другой объект, и ему следует установить значение Current
для этого нового объекта.
void Reset();
указывает тип начать с начала.
Теперь давайте реализуем это.Пожалуйста, найдите время, чтобы просмотреть этот тип IEnumerator
, чтобы вы поняли его.Поймите, что когда вы ссылаетесь на тип IEnumerable
, вы даже не ссылаетесь на IEnumerator
(это);однако вы ссылаетесь на тип, который возвращает IEnumerator
через GetEnumerator()
Примечание: Будьте осторожны, чтобы не перепутать имена.IEnumerator
отличается от IEnumerable
.
IEnumerator
public class MyEnumerator : IEnumerator
{
private string First => nameof(First);
private string Second => nameof(Second);
private string Third => nameof(Third);
private int counter = 0;
public object Current { get; private set; }
public bool MoveNext()
{
if (counter > 2) return false;
counter++;
switch (counter)
{
case 1:
Current = First;
break;
case 2:
Current = Second;
break;
case 3:
Current = Third;
break;
}
return true;
}
public void Reset()
{
counter = 0;
}
}
Теперь давайте создадим тип IEnumerable
и используем этот IEnumerator
.
IEnumerable
public class MyEnumerable : IEnumerable
{
public IEnumerator GetEnumerator() => new MyEnumerator();
}
Это что-то, чтобы впитать ... Когда вы делаете вызов, как numbers.Select(n => n % 2 == 0 ? n : 0)
, вы не выполняете никаких элементов ...Вы возвращаете тип, очень похожий на приведенный выше..Select(…)
возвращает IEnumerable<int>
.Посмотрите выше ... IEnumerable
не что иное, как интерфейс, который вызывает GetEnumerator()
.Это происходит всякий раз, когда вы входите в циклическую ситуацию, или это можно сделать вручную.Итак, имея это в виду, вы уже можете видеть, что итерация никогда не начинается, пока вы не вызовете GetEnumerator()
, и даже тогда она никогда не начнется, пока вы не вызовете MoveNext()
метод результата GetEnumerator()
, который является типом IEnumerator
.
Итак ...
Другими словами, у вас просто есть ссылка на IEnumerable<T>
в вашем вызове и ничего более.Итераций не было.Вот почему код переходит обратно в ваш, потому что он, наконец, выполняет итерацию в методе ElementAt
и затем просматривает выражение lamba.Оставайтесь со мной, и позже я обновлю пример, чтобы завершить этот урок, но сейчас давайте продолжим наш простой пример:
Давайте теперь создадим простое консольное приложение для тестирования наших новых типов.
Консольное приложение
class Program
{
static void Main(string[] args)
{
var myEnumerable = new MyEnumerable();
foreach (var item in myEnumerable)
Console.WriteLine(item);
Console.ReadKey();
}
// OUTPUT
// First
// Second
// Third
}
Теперь давайте сделаем то же самое, но сделаем его общим.Я не буду так много писать, но буду внимательно следить за кодом на предмет изменений, и вы получите его.
Я собираюсь скопировать и вставить все это в одном.
Все консольное приложение
using System;
using System.Collections;
using System.Collections.Generic;
namespace Question_Answer_Console_App
{
class Program
{
static void Main(string[] args)
{
var myEnumerable = new MyEnumerable<Person>();
foreach (var person in myEnumerable)
Console.WriteLine(person.Name);
Console.ReadKey();
}
// OUTPUT
// Test 0
// Test 1
// Test 2
}
public class Person
{
static int personCounter = 0;
public string Name { get; } = "Test " + personCounter++;
}
public class MyEnumerator<T> : IEnumerator<T>
{
private T First { get; set; }
private T Second { get; set; }
private T Third { get; set; }
private int counter = 0;
object IEnumerator.Current => (IEnumerator<T>)Current;
public T Current { get; private set; }
public bool MoveNext()
{
if (counter > 2) return false;
counter++;
switch (counter)
{
case 1:
First = Activator.CreateInstance<T>();
Current = First;
break;
case 2:
Second = Activator.CreateInstance<T>();
Current = Second;
break;
case 3:
Third = Activator.CreateInstance<T>();
Current = Third;
break;
}
return true;
}
public void Reset()
{
counter = 0;
First = default;
Second = default;
Third = default;
}
public void Dispose() => Reset();
}
public class MyEnumerable<T> : IEnumerable<T>
{
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<T> GetEnumerator() => new MyEnumerator<T>();
}
}
Итак, давайте повторим ... IEnumerable<T>
- это тип, у которого есть метод, который возвращает тип IEnumerator<T>
,Тип IEnumerator<T>
имеет свойство T Current { get; }
, а также методы IEnumerator
.
Давайте разберем это еще раз в коде и назовем кусочки вручную, чтобы вы могли видеть это понятнее.Это будет только консольная часть приложения, потому что все остальное остается прежним.
Консольное приложение
class Program
{
static void Main(string[] args)
{
IEnumerable<Person> enumerable = new MyEnumerable<Person>();
IEnumerator<Person> enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
Console.WriteLine(enumerator.Current.Name);
Console.ReadKey();
}
// OUTPUT
// Test 0
// Test 1
// Test 2
}
К вашему сведению: Одна вещь, на которую следует обратить внимание, в ответе выше есть две версии Linq.Linq в EF или Linq-to-SQL содержит методы расширения, отличные от обычных linq.Основное отличие состоит в том, что выражение запроса в Linq (при обращении к базе данных) вернет IQueryable<T>
, который реализует интерфейс IQueryable
, который создает выражения SQL, которые запускаются и повторяются.Другими словами ... что-то вроде предложения .Where(…)
не запрашивает всю базу данных, а затем выполняет итерацию по ней.Превращает это выражение в выражение SQL.Вот почему такие вещи, как .Equals()
, не будут работать в этих конкретных лямбда-выражениях.