Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Технология LINQ to Objects






Платформа.NET версии 3.5 представила новую технологию работы с коллекциями – Language Integrated Query (LINQ). По типу обрабатываемой информации LINQ делится на LINQ to Objects – библиотеки для обработки коллекций объектов в памяти, LINQ to SQL – библиотеки для работы с базами данных, LINQ to XML предназначена для обработки XML-информации. В данном параграфе акцент сделан на LINQ to Objects.

Технически, LINQ to Objects – это набор классов, содержащих типичные методы обработки коллекций: поиск данных, сортировка, фильтрация. Ядром LINQ to Objects является статический класс Enumerable, размещённый в пространстве имён System.Linq[11]. Этот класс содержит набор методов расширения интерфейса IEnumerable< T>, которые в дальнейшем будут называться операторами LINQ. Для удобства дальнейшего изложения используем стандартное деление операторов LINQ на группы в зависимости от выполняемых действий:

1. Оператор условия Where (отложенные вычисления).

2. Операторы проекций (отложенные вычисления).

3. Операторы упорядочивания (отложенные вычисления).

4. Оператор группировки GroupBy (отложенные вычисления).

5. Операторы соединения (отложенные вычисления).

6. Операторы работы с множествами (отложенные вычисления).

7. Операторы агрегирования.

8. Операторы генерирования (отложенные вычисления).

9. Операторы кванторов и сравнения.

10. Операторы разбиения (отложенные вычисления).

11. Операторы элемента.

12. Операторы преобразования.

В примерах параграфа будут использоваться либо коллекции примитивных типов, либо коллекция gr объектов класса Student:

public class Student

{

public string Name { get; set; }

public int Age { get; set; }

public IEnumerable< int> Marks { get; set; }

}

 

var gr = new List< Student> {

new Student {Name = " Smirnov", Age = 18, Marks = new[] {10, 8, 9}},

new Student {Name = " Ivanova", Age = 20, Marks = new[] {5, 6, 9}},

new Student {Name = " Kuznetsov", Age = 18, Marks = new[] {7, 7, 4}},

new Student {Name = " Sokolov", Age = 20, Marks = new[] {7, 8, 8}},

new Student {Name = " Lebedeva", Age = 20, Marks = new[] {9, 9, 9}}

};

1. Оператор условия Where().

Оператор производит фильтрацию коллекции, основываясь на аргументе-предикате. Сигнатуры оператора[12]:

IEnumerable< T> Where< T> (this IEnumerable< T> source,

Func< T, bool> predicate);

 

IEnumerable< T> Where< T> (this IEnumerable< T> source,

Func< T, int, bool> predicate);

Второй вариант оператора Where() позволяет передать аргументу-предикату индекс элемента в коллекции (заметим, что многие другие операторы имеют перегруженную версию, устроенную по такому же принципу).

Примеры использования Where():

var list = new List< int> {1, 3, -1, -4, 7};

var r1 = list.Where(x => x < 0);

 

var r2 = gr.Where(student => student.Age > 19);

var r3 = gr.Where((student, pos) => student.Age > 19 & & pos < 3);

2. Операторы проекций.

Операторы проекций применяются для выборки информации, при этом они могут изменять тип элементов итоговой коллекции. Основным оператором проекции является Select():

IEnumerable< S> Select< T, S> (this IEnumerable< T> source,

Func< T, S> selector);

Оператор SelectMany() может применяться, если результатом проекции является набор данных. В этом случае оператор соединяет все элементы набора в одну коллекцию.

IEnumerable< S> SelectMany< T, S> (this IEnumerable< T> source,

Func< T, IEnumerable< S> > selector);

Примеры использования операторов проекций:

var r1 = gr.Select(student => student.Name);

var r2 = gr.Select(student => new {student.Name, student.Age});

var r3 = gr.SelectMany(student => student.Marks);

Коллекция r1 будет содержать имена студентов. Коллекция r2 состоит из объектов анонимного типа с полями Name и Age. Коллекция r3 – это все оценки студентов (15 элементов типа int).

3. Операторы упорядочивания.

Операторы OrderBy() и OrderByDescending() выполняют сортировку коллекции по возрастанию или убыванию соответственно. Имеется версия данных операторов, принимающая в качестве дополнительного аргумента объект, реализующий IComparer< T>.

IOrderedEnumerable< T> OrderBy< T, K> (this IEnumerable< T> source,

Func< T, K> keySelector);

 

IOrderedEnumerable< T> OrderByDescending< T, K> (

this IEnumerable< T> source, Func< T, K> keySelector);

Интерфейс IOrderedEnumerable< T> является наследником IEnumerable< T> и описывает упорядоченную последовательность элементов с указанием на ключ сортировки. Если после выполнения сортировки по одному ключу требуется дополнительная сортировка по другому ключу, нужно воспользоваться операторами ThenBy() и ThenByDescending(). Имеется также оператор Reverse(), обращающий коллекцию.

Пример использования операторов упорядочивания:

var r1 = Enumerable.Reverse(gr);

var r2 = gr.OrderBy(student => student.Age);

var r3 = gr.OrderByDescending(student => student.Age)

.ThenBy(student => student.Name);

Чтобы получить коллекцию r1, метод расширения использовался как обычный статический метод класса, так как у List< T> имеется собственный метод Reverse(). В коллекции r3 студенты упорядочены по убыванию возраста, а при совпадении возрастов – по фамилиям в алфавитном порядке.

4. Оператор группировки GroupBy().

Оператор группировки GroupBy() разбивает коллекцию на группы элементов с одинаковым значением некоторого ключа. Оператор GroupBy() имеет перегруженные версии, позволяющие указать селектор ключа, преобразователь элементов в группе, объект, реализующий IEqualityComparer< T> для сравнения ключей. Простейшая версия оператора группировки имеет следующий вид:

IEnumerable< IGrouping< K, T> > GroupBy< T, K> (this IEnumerable< T> src,

Func< T, K> keySelector);

Здесь последний параметр указывает на функцию, которая строит по элементу поле-ключ. Обычно эта функция просто выбирает одно из полей объекта. Интерфейс IGrouping< K, T> унаследован от IEnumerable< T> и содержит дополнительное типизированное свойство Key – ключ группировки.

Рассмотрим применение оператора группировки. Сгруппируем студентов по возрасту и для каждой группы возрастов выведем ключ и элементы:

var r1 = gr.GroupBy(student => student.Age);

foreach (IGrouping< int, Student> group in r1)

{

Console.WriteLine(group.Key);

foreach (Student student in group)

{

Console.WriteLine(student.Name);

}

}

5. Операторы соединения.

Операторы соединения применяются, когда требуется соединить две коллекции, элементы которых имеют общие атрибуты. Основным оператором соединения является оператор Join().

IEnumerable< V> Join< T, U, K, V> (this IEnumerable< T> outer,

IEnumerable< U> inner,

Func< T, K> outerKeySelector,

Func< U, K> innerKeySelector,

Func< T, U, V> resultSelector);

Оператор Join() требует задания двух коллекций (внешней и внутренней) и трёх функций. Первая функция порождает ключ из элемента внешней коллекции, вторая – из элемента внутренней коллекции, а третья функция продуцирует объект коллекции-результата. При выполнении соединения Join() итерируется по внешней коллекции и ищет соответствия с элементами внутренней коллекции. При этом возможны следующие ситуации:

1. Найдено одно соответствие – в результат включается один элемент.

2. Найдено множественное соответствие – результат содержит по элементу для каждого соответствия.

3. Соответствий не найдено – элемент не входит в результат.

Рассмотрим примеры использования оператора Join(). Для этого опишем коллекцию cit объектов класса Citizen.

public class Citizen

{

public int BirthYear { get; set; }

public string IDNumber { get; set; }

}

 

int year = DateTime.Now.Year;

var cit = new List< Citizen> {

new Citizen {BirthYear = year - 17, IDNumber = " KLM897" },

new Citizen {BirthYear = year - 18, IDNumber = " WEF442" },

new Citizen {BirthYear = year - 18, IDNumber = " HHH888" },

new Citizen {BirthYear = year - 25, IDNumber = " XYZ012" }};

Выполним оператор Join():

var r1 = gr.Join(cit,

student => year - student.Age,

citizen => citizen.BirthYear,

(student, citizen) => new {student.Name, citizen.IDNumber});

 

// r1 содержит следующие объекты:

// {Name = " Smirnov", IDNumber = " WEF442" }

// {Name = " Smirnov", IDNumber = " HHH888" }

// {Name = " Kuznetsov", IDNumber = " WEF442" }

// {Name = " Kuznetsov", IDNumber = " HHH888" }

Оператор GroupJoin() порождает набор, группируя элементы внутренней коллекции при нахождении соответствия с элементом внешней коллекции: Если же соответствие не найдено, в результат включается пустая группа.

IEnumerable< V> GroupJoin()< T, U, K, V> (this IEnumerable< T> outer,

IEnumerable< U> inner,

Func< T, K> outerKeySelector,

Func< U, K> innerKeySelector,

Func< T, IEnumerable< U>, V> resultSelector);

Ниже приведён пример использования GroupJoin().

var r3 = cit.GroupJoin(gr,

citizen => citizen.BirthYear,

student => year - student.Age,

(citizen, group) => new {citizen.IDNumber,

Names = group.Select(st => st.Name)});

foreach (var data in r3)

{

Console.WriteLine(data.IDNumber);

foreach (string s in data.Names)

{ // 1-я и 4-я группы пусты,

Console.WriteLine(s); // 2-я и 3-я содержат по два элемента

}

}

Оператор Zip() порождает набор на основе двух исходных коллекций, выполняя заданное генерирование элементов. Длина результирующей коллекции равна длине меньшей из двух исходных коллекций.

IEnumerable< V> Zip< T, U, V> (this IEnumerable< T> first,

IEnumerable< U> second,

Func< T, U, V> resultSelector);

Дадим простейший пример использования Zip():

int[] numbers = {1, 2, 3, 4};

string[] words = {" one", " two", " three" };

var res = numbers.Zip(words, (f, s) => f + " =" + s); // 3 элемента

6. Операторы работы с множествами.

В LINQ to Objects имеется набор операторов для работы с множествами.

IEnumerable< T> Distinct< T> (this IEnumerable< T> source);

 

IEnumerable< T> Union< T> (this IEnumerable< T> first,

IEnumerable< T> second);

 

IEnumerable< T> Intersect< T> (this IEnumerable< T> first,

IEnumerable< T> second);

 

IEnumerable< T> Except< T> (this IEnumerable< T> first,

IEnumerable< T> second);

Оператор Distinct() удаляет из коллекции повторяющиеся элементы. Операторы Union(), Intersect() и Except() представляют объединение, пересечение и разность двух множеств.

7. Операторы агрегирования.

К операторам агрегирования относятся операторы, результатом работы которых является скалярное значение. Следующие операторы возвращают количество элементов коллекции. При этом может быть использована перегруженная версия, принимающая в качестве второго аргумента предикат фильтрации.

int Count< T> (this IEnumerable< T> source);

 

long LongCount< T> (this IEnumerable< T> source);

Следующие операторы подсчитывают сумму и среднее значение в коллекции. При этом Num должен быть типом int, long, float, double, decimal или вариантом этих типов с поддержкой null (например, long?).

Num Sum(this IEnumerable< Num> source);

 

Num Sum< T> (this IEnumerable< T> source, Func< T, Num> selector);

 

Num Average(this IEnumerable< Num> source);

 

Num Average< T> (this IEnumerable< T> source, Func< T, Num> selector);

Существует также несколько перегруженных версий операторов для нахождения минимального и максимального значений. Первые две версии применяются для коллекций с числовым элементом. Последние две предполагают, что элемент коллекции реализует интерфейс IComparable< T>.

Num Min/Max(this IEnumerable< Num> source);

 

Num Min< T> /Max< T> (this IEnumerable< T> src, Func< T, Num> selector);

 

T Min< T> /Max< T> (this IEnumerable< T> source);

 

S Min< T, S> /Max< T, S> (this IEnumerable< T> src, Func< T, S> selector);

Оператор Aggregate() позволяет выполнить для коллекции собственный алгоритм агрегирования. Его простейшая форма:

T Aggregate< T> (this IEnumerable< T> source, Func< T, T, T> func);

Функция func принимает два аргумента: значение-аккумулятор и текущее значение коллекции. Результат функции перезаписывается в аккумулятор. В следующем примере оператор Aggregate() применяется для обращения строки:

var text = " The quick brown fox jumps over the lazy dog";

string[] words = text.Split(' ');

var reversed = words.Aggregate((acc, next) => next + " " + acc);

8. Операторы генерирования.

Эта группа операторов позволяет создать набор данных. Первый оператор группы – оператор Range(). Он просто выдаёт указанное количество подряд идущих целых чисел, начиная с заданного значения.

IEnumerable< int> Range(int start, int count);

Продемонстрируем использование Range() в задаче поиска простых чисел, которую решим при помощи LINQ:

var primes = Enumerable.Range(2, 999)

.Where(x =>! Enumerable.Range(2, (int) Math.Sqrt(x))

.Any(y => x! = y & & x%y == 0));

foreach (var prime in primes)

{

Console.WriteLine(prime);

}

Следующий оператор генерирования – оператор Repeat(). Он создаёт коллекцию, в которой указанный элемент повторяется требуемое число раз. Для ссылочных типов дублируются ссылки, а не содержимое.

IEnumerable< T> Repeat< T> (T element, int count);

Покажем не совсем стандартное применение Repeat() для генерирования последовательности случайных чисел:

Random rnd = new Random();

var r1 = Enumerable.Repeat(0, 20).Select(i => rnd.Next(0, 40));

Последним генерирующим оператором является оператор Empty(), который порождает пустое перечисление определённого типа.

IEnumerable< T> Empty< T> ();

9. Операторы кванторов и сравнения.

Операторы кванторов похожи на соответствующие операторы в математической логике.

bool Any< T> (this IEnumerable< T> source, Func< T, bool> predicate);

 

bool Any< T> (this IEnumerable< T> source);

 

bool All< T> (this IEnumerable< T> source, Func< T, bool> predicate);

 

bool Contains< T> (this IEnumerable< T> source, T value);

 

bool Contains< T> (this IEnumerable< T> source, T value,

IEqualityComparer< T> comparer)

Оператор Any() проверяет наличие хотя бы одного элемента в коллекции, удовлетворяющего указанному предикату. Вторая версия оператора Any() просто проверяет коллекцию на непустоту. Оператор All() возвращает true, если все элементы коллекции удовлетворяют предикату. И, наконец, оператор Contains() проверяет, входит ли заданное значение в коллекцию.

Оператор SequenceEqual() сравнивает две коллекции и возвращает true, если обе коллекции имеют одинаковую длину, и их соответствующие элементы равны:

bool SequenceEqual< T> (this IEnumerable< T> first,

IEnumerable< T> second);

 

bool SequenceEqual< T> (this IEnumerable< T> first,

IEnumerable< T> second,

IEqualityComparer< T> comparer);

10. Операторы разбиения.

Операторы разбиения выделяют некую часть исходной коллекции (например, первые десять элементов).

IEnumerable< T> Take< T> (this IEnumerable< T> source, int count);

 

IEnumerable< T> TakeWhile< T> (this IEnumerable< T> source,

Func< T, bool> predicate);

 

IEnumerable< T> Skip< T> (this IEnumerable< T> source, int count);

 

IEnumerable< T> SkipWhile< T> (this IEnumerable< T> source,

Func< T, bool> predicate);

Комбинация операторов Take() и Skip() часто применяется, чтобы организовать постраничный просмотр информации (например, просмотр записей из большой таблицы):

var bigTable = Enumerable.Range(1, 1000);

int pageSize = 20, pageNumber = 6;

var page = bigTable.Skip((pageNumber - 1)*pageSize).Take(pageSize);

11. Операторы элемента.

Эта группа операторов предназначена для выделения из коллекции единственного элемента, удовлетворяющего определённым условиям.

Оператор First() выделяет первый элемент (или первый элемент, удовлетворяющий определённому предикату).

T First< T> (this IEnumerable< T> source);

 

T First< T> (this IEnumerable< T> source, Func< T, bool> predicate);

Если в коллекции нет элементов, или не нашлось элементов, удовлетворяющих предикату, оператор First() выбрасывает исключение InvalidOperationException. Если требуется, чтобы исключение не выбрасывалось, а возвращалось предопределённое значение для типа, следует использовать оператор FirstOrDefault().

T FirstOrDefault< T> (this IEnumerable< T> source);

 

T FirstOrDefault< T> (this IEnumerable< T> source,

Func< T, bool> predicate);

Аналогично работают операторы Last() и LastOrDefault() для выделения последнего элемента.

Операторы Single() и SingleOrDefault() рассчитаны на то, что коллекция (или набор элементов, удовлетворяющих предикату) будет состоять из одного элемента, который данные операторы и возвращают. Если в коллекции нет элементов, или их оказалось больше одного, оператор Single() выбрасывает исключение InvalidOperationException. Оператор SingleOrDefault() выбрасывает такое исключение, если элементов оказалось больше одного.

Пара операторов ElementAt() и ElementAtOrDefault() пытаются вернуть элемент на указанной целочисленной позиции.

Оператор DefaultIfEmpty() проверяет коллекцию на пустоту. Если в коллекции нет элементов, то возвращается или значение по умолчанию для типа, или указанное значение. Если коллекция непустая, то она и возвращается.

IEnumerable< T> DefaultIfEmpty< T> (this IEnumerable< T> source);

 

IEnumerable< T> DefaultIfEmpty< T> (this IEnumerable< T> source,

T defaultValue);

12. Операторы преобразования.

Назначение данных операторов – преобразования универсальных коллекций, реализующих IEnumerable< T>, в конкретные типы.

Оператор AsEnumerable() возвращает свой аргумент, приведённый к типу IEnumerable< T>. Этот оператор необходим в том случае, если тип аргумента source имеет методы, аналогичные имеющимся в LINQ to Objects.

IEnumerable< T> AsEnumerable< T> (this IEnumerable< T> source);

Операторы ToArray() и ToList() выполняют преобразование коллекции в массив или список.

T[] ToArray< T> (this IEnumerable< T> source);

 

List< T> ToList< T> (this IEnumerable< T> source);

Существует несколько версий оператора ToDictionary(), порождающего из коллекции словарь. Например, следующая версия использует отдельные функции для выделения из элемента коллекции ключа и значения ключа.

Dictionary< K, V> ToDictionary< T, K, V> (this IEnumerable< T> source,

Func< T, K> keySelector,

Func< T, V> valueSelector);

Оператор ToLookup() преобразовывает коллекцию в объект класса Lookup< K, V> из пространства имён System.Linq. Класс Lookup< K, V> представляет словарь, в котором ключу сопоставлен не единственный элемент, а набор элементов.

ILookup< K, V> ToLookup< T, K, V> (this IEnumerable< T> source,

Func< T, K> keySelector,

Func< T, V> valueSelector);

Оператор OfType< T> () итерируется по коллекции и генерирует список, содержащий только элементы заданного типа T.

IEnumerable< T> OfType< T> (this IEnumerable source);

Оператор Cast< T> () итерируется по слаботипизированной коллекции и пытается выполнить приведение каждого элемента к указанному типу. Если приведение выполнить не удаётся, генерируется исключение. Данный оператор полезен, если мы хотим применять LINQ для старых коллекций, таких, как ArrayList.

IEnumerable< T> Cast< T> (this IEnumerable source);

Из операторов преобразования отложенные вычисления выполняют операторы AsEnumerable< T> (), OfType< T> (), Cast< T> ().

Язык C# версии 3.0 вводит новые ключевые слова и особые синтаксические расширения для записи некоторых операторов LINQ в виде выражений запросов. Составленное выражение запросов должно подчиняться следующим правилам (за строгим описанием правил следует обратиться к MSDN):

1. Выражение должно начинаться с конструкции from, которая указывает на обрабатываемую коллекцию.

2. Затем выражение может содержать ноль или более конструкции from, let или where. Конструкция let представляет переменную и присваивает ей значение. Конструкция where фильтрует элементы коллекции.

3. Затем выражение может включать ноль или более конструкций orderby, с полями сортировки и необязательным указанием на направление упорядочивания. Направление может быть ascending или descending.

4. Затем следует конструкция select или group.

5. Наконец, в оставшейся части выражения может следовать необязательная конструкция продолжения. Такой конструкцией является into.

Выражения запросов транслируются компилятором в вызовы соответствующих операторов LINQ. Приведём некоторые примеры эквивалентной записи запросов к коллекциям данных.

var r1 = gr.Where(s => s.Age > 20).Select(s => s.Marks);

// эквивалентный синтаксис

var r2 = from s in gr

where s.Age > 20

select s.Marks;

 

var r3 = gr.OrderByDescending(s => s.Age).ThenBy(s => s.Name);

// эквивалентный синтаксис

var r4 = from s in gr

orderby s.Age descending, s.Name

select s;

 

var r5 = gr.Select(student => new {student.Name, student.Age});

// эквивалентный синтаксис

var r6 = from student in gr select new {student.Name, student.Age};

Приведём несколько примеров использования технологии LINQ to Objects. В первом примере подсчитывается частота вхождения каждого слова в текст.

var text = " this is the text and this is the code for the text";

var words = text.Split(' ');

var result = words.GroupBy(word => word.ToUpper())

.Select(group => new {group.Key, Count = group.Count()})

.OrderBy(pair => pair.Count);

 

foreach (var pair in result)

Console.WriteLine(" {0} - {1}", pair.Count, pair.Key);

Второй пример – метод расширения, демонстрирующий идею алгоритма быстрой сортировки (так как LINQ to Objects использует отложенные вычисления, метод выполняется медленно).

public static IEnumerable< T> QSort< T> (this IEnumerable< T> data)

where T: IComparable< T>

{

var tail = data.Skip(1);

if (tail.Any())

{

var elem = data.First();

var head = data.Take(1);

var left = tail.Where(x => x.CompareTo(elem) < 0).QSort();

var right = tail.Where(x => x.CompareTo(elem) > = 0).QSort();

return left.Concat(head).Concat(right);

}

return data;

}






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.