
- •1 Основные сведения о C#
- •1.1 Особенности языка
- •1.2 Типы данных
- •1.3 Переменные
- •1.4 Константы (литералы)
- •1.5 Операторы, используемые при построении выражений
- •1.6 Класс Object
- •1.7 Класс Math
- •1.8 Класс Convert
- •1.9 Пространство имён
- •1.10 Типы, допускающие значение null
- •2 Операторы и конструкции С#
- •2.1 Операторы присваивания
- •2.2 Приведение типов
- •2.3 Операторы инкремента и декремента
- •2.4 Операторные скобки {}
- •2.5 Условный оператор if
- •2.6 Логические операторы «И» и «ИЛИ»
- •2.7 Условный оператор ? :
- •2.8 Оператор выбора switch и оператор прерывания break
- •2.9 Оператор цикла for
- •2.10 Оператор цикла while
- •2.11 Оператор цикла do...while
- •2.12 Операторы прерываний break (для циклов) и continue
- •2.13 Оператор new
- •2.14 Массивы
- •2.14.1 Одномерные массивы
- •2.14.2 Многомерные массивы
- •2.14.3 Ступенчатые массивы
- •2.14.4 Работа с массивами как с объектами
- •2.15 Оператор цикла foreach
- •2.16 Строки
- •2.17 Перечисления
- •2.18 Обработка исключений
- •2.18.1 Класс Exception и стандартные исключения
- •2.18.2 Блок try...catch
- •2.18.3 Блок try...finally
- •2.18.4 Блок try...catch...finally
- •2.18.5 Оператор throw
- •3 Классы. Основные понятия
- •3.1 Общая схема
- •3.2 Спецификаторы доступа
- •3.3 Поля
- •3.4 Создание объекта и доступ к его членам
- •3.5 Методы
- •3.5.1 Перегрузка методов
- •3.5.2 Новое в версии C# 4.0
- •3.6 Конструкторы
- •3.7 Деструкторы
- •3.8 Инициализаторы объектов
- •3.9 Свойства
- •3.10 Индексаторы
- •4 Классы. Расширенное использование
- •4.1 Статические классы и члены классов
- •4.2 Наследование
- •4.2.1 Наследование и конструкторы
- •4.2.2 Переопределение членов класса
- •4.3 Полиморфизм
- •4.3.1 Виртуальные методы
- •4.3.2 Абстрактные классы и члены классов
- •4.3.3 Операторы as и is
- •4.3.4 Модификатор sealed
- •4.4 Перегрузка операторов
- •5 Интерфейсы
- •6 Делегаты, лямбда-выражения и события
- •6.1 Делегаты
- •6.2 Анонимные методы и лямбда-выражения
- •6.3 События
- •7 Универсальные типы
- •7.1 Общая схема
- •7.2 Ограничения по параметрам типа
- •7.2.1 Ограничение на базовый класс
- •7.2.2 Ограничение на интерфейс
- •7.2.3 Ограничение на конструктор
- •7.2.4 Ограничения ссылочного типа и типа значения
- •7.3 Параметры типы в методах
- •7.4 Некоторые универсальные типы С#
- •7.4.1 Класс Array
- •7.4.2 Класс List<T>
- •7.4.3 Класс LinkedList<T>
- •7.4.4 Класс Queue<T>
- •7.4.5 Класс Stack<T>
- •7.4.6 Классы SortedSet<T> и HashSet<T>
- •7.4.7 Классы Dictionary<TKey, TValue> и SortedDictionary<TKey, TValue>
- •8 Работа с файлами
- •8.1 Класс File
- •8.2 Работа с файлами как с потоками
- •8.2.1 Класс FileStream
- •8.2.2 Класс StreamReader
- •8.2.3 Класс StreamWriter
- •8.2.4 Класс BinaryReader
- •8.2.5 Класс BinaryWriter
- •9 LINQ
- •9.1 Программные конструкции и типы, используемые LINQ
- •9.1.1 Методы расширения
- •9.1.2 Анонимные типы
- •9.1.3 Интерфейс IEnumerable<T>
- •9.2 Построение запросов на LINQ
- •9.2.1 Общая структура запроса
- •9.2.2 Простой запрос
- •9.2.3 where : использование условий отбора
- •9.2.4 orderby : использование сортировки
- •9.2.5 select : определение возвращаемого значения
- •9.2.1 group : группировка данных
- •9.2.1 into : обработка результатов группировки
- •9.2.1 let : временные переменные в запросе
- •9.2.2 from : использование нескольких источников данных
- •9.2.3 join : соединение данных из нескольких источников
- •9.3 Получение результатов с использованием методов
- •9.3.1 Метод Where
- •9.3.2 Метод Select
- •9.3.3 Методы сортировки
- •9.3.4 Метод GroupBy
- •9.3.5 Метод Join
- •9.3.6 Дополнительные методы интерфейса IEnumerable<T>
- •9.4 Совместное использование запросов и методов
Peoples[5] = new People() { Surname = "Попов", YearOfBirth = 1980, City = "Новокузнецк" };
var Result = from p in Peoples
orderby p.City, p.YearOfBirth, p.Surname
group p by new { city = p.City, year = p.YearOfBirth }; Result_TB.Clear();
foreach (var group in Result)
{
Result_TB.AppendText(group.Key.city + ", " + group.Key.year + "\n");
foreach (var p in group) |
|
Result_TB.AppendText(" |
" + p.Surname + "\n"); |
} |
|
Результат будет выведен в виде:
Москва, 1980 Степанов Москва, 1999
Петров Новокузнецк, 1980
Иванов
Попов Новокузнецк, 2001
Антонов Псков, 1971
Иванов
В данном примере в операторе
group p by new { city = p.City, year = p.YearOfBirth }
создается анонимный тип с полями city и year, заполнение которых осуществляется из p.City и p.YearOfBirth соответственно.
При выводе значения ключа
Result_TB.AppendText(group.Key.city + ", " + group.Key.year + "\n");
используется обращение к одноименным полям, которые, естественно, имеются у ключа группы.
9.2.1 into : обработка результатов группировки
Результаты запроса, использующего группировку, могут также подвергаться обработке, как и данные из <источника данных>.
Для этого оператор группировки записывается в формате
group <возвращаемые данные> by <ключ группировки> into <переменная группы>
154
где <переменная группы> – переменная, с которой далее могут выполняться различные действия с использованием операторов where, orderby, select. Фактически создание <переменной группы> начинает новый запрос по обработке данных группы, который также должен заканчиваться операторами select или droup.
Пример: сгруппировать данные о людях по городу их проживания и вывести в алфавитном порядке названий городов. В пределах каждого города выводить людей в алфавитном порядке фамилий. Выводить данные только по тем городам, где проживает более одного человека. Вывод осуществлять в компонент класса
TextBox.
People[] Peoples = new People[6];
Peoples[0] = new People() { Surname = "Иванов", YearOfBirth = 1980, City = "Новокузнецк" };
Peoples[1] = new People() { Surname = "Петров", YearOfBirth = 1999, City = "Москва" };
Peoples[2] = new People() { Surname = "Иванов", YearOfBirth = 1971, City = "Псков" };
Peoples[3] = new People() { Surname = "Степанов", YearOfBirth = 1980, City = "Москва" };
Peoples[4] = new People() { Surname = "Антонов", YearOfBirth = 2001, City = "Новокузнецк" };
Peoples[5] = new People() { Surname = "Попов", YearOfBirth = 1980, City = "Новокузнецк" };
var Result = from p in Peoples orderby p.Surname
group p by p.City into g where g.Count() > 1 orderby g.Key
select g; Result_TB.Clear();
foreach (var group in Result)
{
Result_TB.AppendText(group.Key + "\n"); foreach (var p in group)
Result_TB.AppendText(" "+p.Surname + "\n");
}
Результат будет выведен в виде:
Москва
Петров
Степанов
Новокузнецк
Антонов
Иванов
Попов
155
9.2.1 let : временные переменные в запросе
При выполнении запроса может потребоваться выполнять одни и те же вычисления несколько раз. В этом случае целесообразно один раз выполнить выражение, сохранить его результат во временную переменную, которую и использовать в дальнейшем. Создание временной переменной внутри запроса реализуеься по схеме:
let <переменной> = <значение>
Пример: сгруппировать данные о людях по длине их фамилии и вывести в порядке возрастания длин. В пределах каждой группы выводить людей в алфавитном порядке фамилий. Вывод осуществлять в компонент Result_TB класса TextBox.
People[] Peoples = new People[6];
Peoples[0] = new People() { Surname = "Иванов", YearOfBirth = 1980, City = "Новокузнецк" };
Peoples[1] = new People() { Surname = "Петров", YearOfBirth = 1999, City = "Москва" };
Peoples[2] = new People() { Surname = "Иванов", YearOfBirth = 1971, City = "Псков" };
Peoples[3] = new People() { Surname = "Степанов", YearOfBirth = 1980, City = "Москва" };
Peoples[4] = new People() { Surname = "Антонов", YearOfBirth = 2001, City = "Новокузнецк" };
Peoples[5] = new People() { Surname = "Попов", YearOfBirth = 1980, City = "Новокузнецк" };
var Result = from p in Peoples
let b = p.Surname.Length orderby p.Surname
group p by b into g orderby g.Key select g;
Result_TB.Clear();
foreach (var group in Result)
{
Result_TB.AppendText(group.Key + "\n"); foreach (var p in group)
Result_TB.AppendText(" "+p.Surname + "\n");
}
Результат будет выведен в виде:
5
Попов
6
Иванов
Иванов
Петров
7
Антонов
156
8
Степанов
9.2.2 from : использование нескольких источников данных
Оператор from в LINQ во многом аналогичен оператору цикла foreach, т.к. он организует перебор элементов в <источнике запросов> для их последующей обработки. Поэтому последовательное расположение нескольких операторов from фактически реализует вложенные циклы, обеспечивающие формирование всех возможных комбинаций элементов (декартово произведение).
Пример: построение таблицы перемножения всех возможных пар элементов двух одномерных целочисленных массивов.
int[] Mas1 = { 1, 2 };
int[] Mas2 = { 1, 2, 3, 4 }; var Result = (from c1 in Mas1 from c2 in Mas2
select new { C1 = c1, C2 = c2, Pr = c1 * c2 }).ToArray(); // Result = {[1, 1, 1], [1, 2, 2], [1, 3, 3], [1, 4, 4],
[2, 1, 2], [2, 2, 4], [2, 3, 6], [2, 4, 8]}
В большинстве случаев не требуется получать полное декартово произведение. Поэтому в запросе используются условия отбора.
Пример: построение таблицы перемножения всех возможных пар элементов двух одномерных целочисленных массивов, исключая случай, когда элементы массивов равны.
int[] Mas1 = { 1, 2 };
int[] Mas2 = { 1, 2, 3, 4 }; var Result = (from c1 in Mas1 from c2 in Mas2 where c1 != c2
select new { C1 = c1, C2 = c2, Pr = c1 * c2 }).ToArray(); // Result = {[1, 2, 2], [1, 3, 3], [1, 4, 4],
[2, 1, 2], [2, 3, 6], [2, 4, 8]}
9.2.3 join : соединение данных из нескольких источников
Если в предыдущем примере оператор != заменить на оператор ==, то будет произведен отбор пар элементов, имеющих одинаковое значение.
Задача получения пар элементов, имеющих одинаковые значения некоторых данных в разных источниках данных, получила название «соединение», и для нее в LINQ имеется специальный оператор.
Простейшая схема соединения для двух источников данных (внутреннее со-
единение) имеет вид:
157
from <переменная диапазона 1> in <источник данных 1> join <переменная диапазона 2> in <источник данных 2> on <переменная диапазона 1>.<свойство> equals
<переменная диапазона 2>.<свойство>
При выполнении данного оператора будут из двух источников отбираться
только |
те |
пары |
элементов, |
у |
которых |
значение |
|
<переменная диапазона 1>.<свойство> |
совпадает |
со |
значением |
<переменная диапазона 2>.<свойство>.
Для рассмотрения приемов работы по соединению данных будем использовать два класса: Student и Group.
class Group |
|
{ |
|
public int ID; |
// Уникальный идентификатор группы |
public string Name; |
// Наименование группы |
} |
|
class Student |
|
{ |
|
public string Surname; |
// Фамилия студента |
public int YearOfBirth; |
// Год рождения студента |
public int GroupID; |
// Принадлежность к группе |
} |
|
Пример: вывести студентов в алфавитном порядке фамилий с указанием в скобках группы обучения. Вывод осуществлять в компонент Result_TB класса
TextBox.
Group[] Groups = new Group[4];
Groups[0] = new Group() { ID = 1, Name = "АБВ" };
Groups[1] = new Group() { ID = 2, Name = "ННН" };
Groups[2] = new Group() { ID = 3, Name = "ЯАП" };
Groups[3] = new Group() { ID = 4, Name = "КЕП" };
Student[] Students = new Student[6];
Students[0] = new Student() { Surname = "Иванов",
YearOfBirth = 1990, GroupID = 1 };
Students[1] = new Student() { Surname = "Петров",
YearOfBirth = 1985, GroupID = 4 };
Students[2] = new Student() { Surname = "Смирнов",
YearOfBirth = 1990, GroupID = 3 }; Students[3] = new Student() { Surname = "Антонов",
YearOfBirth = 1993, GroupID = 1 };
Students[4] = new Student() { Surname = "Иванов",
YearOfBirth = 1993, GroupID = 4 }; Students[5] = new Student() { Surname = "Петров",
YearOfBirth = 1992, GroupID = 4 };
var Result = from g in Groups
join s in Students on g.ID equals s.GroupID orderby s.Surname
select new { gName = g.Name, sName = s.Surname };
158
Result_TB.Clear(); foreach (var p in Result)
Result_TB.AppendText(p.sName + " (" + p.gName + ")\n");
Результат будет выведен в виде:
Антонов (АБВ) Иванов (АБВ) Иванов (КЕП) Петров (КЕП) Петров (КЕП) Смирнов (ЯАП)
При выполнении соединения можно провести группировку данных (групповое соединение). При этом группа создается для каждого элемента, стоящего в левом источнике данных. В группу попадают элементы из правого источника, подлежащие соединению с левым (если таких элементов нет, то группа оказывается пустой, но она существует).
Для указания необходимости группировки условие группировки дополняется оператором into в соответствии со следующей схемой:
on <переменная диапазона 1>.<свойство> equals
<переменная диапазона 2>.<свойство> into <переменная группы>
<переменная группы> представляет собой интерфейс IEnumerable<T>, где
<T> соответствует типу <переменной диапазона 2>.
Пример: вывести студентов, сгруппировав их по группам. Группы выводить в алфавитном порядке названия. Вывод осуществлять в компонент Result_TB класса
TextBox.
Group[] Groups = new Group[4];
Groups[0] = new Group() { ID = 1, Name = "АБВ" };
Groups[1] = new Group() { ID = 2, Name = "ННН" };
Groups[2] = new Group() { ID = 3, Name = "ЯАП" };
Groups[3] = new Group() { ID = 4, Name = "КЕП" };
Student[] Students = new Student[6];
Students[0] = new Student() { Surname = "Иванов",
YearOfBirth = 1990, GroupID = 1 };
Students[1] = new Student() { Surname = "Петров",
YearOfBirth = 1985, GroupID = 4 };
Students[2] = new Student() { Surname = "Смирнов",
YearOfBirth = 1990, GroupID = 3 };
Students[3] = new Student() { Surname = "Антонов",
YearOfBirth = 1993, GroupID = 1 };
Students[4] = new Student() { Surname = "Иванов",
YearOfBirth = 1993, GroupID = 4 };
Students[5] = new Student() { Surname = "Петров",
YearOfBirth = 1992, GroupID = 4 };
159
var Result = from g in Groups
join s in Students on g.ID equals s.GroupID into r orderby g.Name
select new
{
gName = g.Name, students = r
}; Result_TB.Clear();
foreach (var group in Result)
{
Result_TB.AppendText(group.gName + "\n"); foreach (var p in group.students)
Result_TB.AppendText(" " + p.Surname + "\n");
}
Результат будет выведен в виде:
АБВ
Иванов
Антонов
КЕП
Петров
Иванов
Петров
ННН
ЯАП
Смирнов
Для того, чтобы вывести студентов в пределах группы в алфавитном порядке фамилий, требуется отсортировать переменную группы r. Это можно сделать с помощью вложенного запроса:
var Result = from g in Groups
join s in Students on g.ID equals s.GroupID into r orderby g.Name
select new
{
gName = g.Name, students = from p in r
orderby p.Surname select p
}; Result_TB.Clear();
foreach (var group in Result)
{
Result_TB.AppendText(group.gName + "\n"); foreach (var p in group.students)
Result_TB.AppendText(" " + p.Surname + "\n");
}
Результат будет выведен в виде:
160
АБВ
Антонов
Иванов
КЕП
Иванов
Петров
Петров
ННН
ЯАП
Смирнов
Как видно из примера, в группе «ННН» нет студентов.
Если при такой ситуации требуется вывести в элемент с нужными значениями по умолчанию, то используется левое внешнее соединение. Для реализации такого соединения переменной группы задаются требуемые значения по умолчанию с помощью метода DefaultIfEmpty(): (для примера, значение имени студента будет задано «Нет студентов»)
var Result = from g in Groups
join s in Students on g.ID equals s.GroupID into r orderby g.Name
select new
{
gName = g.Name,
students = from p in r.DefaultIfEmpty( new Student()
{GroupID = 0,
Surname = "Нет студентов",
YearOfBirth = 0
})
orderby p.Surname select p
}; Result_TB.Clear();
foreach (var group in Result)
{
Result_TB.AppendText(group.gName + "\n"); foreach (var p in group.students)
Result_TB.AppendText(" " + p.Surname + "\n");
}
Результат будет выведен в виде:
АБВ
Антонов
Иванов
КЕП
Иванов
Петров
Петров
ННН
Нет студентов
161