C#. Windows Forms. Розробка програми демонстрації використання запитів на мові LINQ




Розробка програми демонстрації використання запитів на мові LINQ

У даній темі розглядається покроковий процес розв’язку задачі з використанням запитів на мові LINQ. Використовуючи даний приклад можна розв’язувати подібні задачі.

У темі також показано:

  • читання даних з файлу з використанням класу StreamReader;
  • представлення даних у вигляді узагальненого динамічного масиву List<T>;
  • приклади деяких запитів на мові LINQ, що розв’язують дану задачу.

Дана тема висвітлює виконання лабораторної роботи в одному з навчальних закладів.


Зміст


Пошук на інших ресурсах:

Умова задачі

Файл “Workers.txt“ містить наступну інформацію про співробітників:

  • ідентифікаційний код;
  • прізвище та ініціали;
  • вид освіти;
  • фах;
  • рік народження.

Файл “Salary.txt“ містить:

  • ідентифікаційний код;
  • зарплату за перше півріччя;
  • зарплату за друге півріччя.

Завантажити файл “Workers.txt” можна тут. Завантажити файл “Salary.txt” можна тут.

Розв’язати наступні задачі:

  1. Вивести прізвища та ініціали співробітників вище 35 років.
  2. Вивести ідентифікаційний код співробітника з найбільшою зарплатою за друге півріччя.
  3. Вивести прізвища, ініціали та вид освіти тих співробітників, зарплата яких за рік нижче середньої за рік.

Завдання мають бути виконані з використанням запитів LINQ.

  ⇑

Додаткові міркування

Як видно з структур файлів “Workers.txt” та “Salary.txt“, ці файли мають однакове поле “ідентифікаційний код”. Це означає, що при вводі даних у файли потрібно бути уважним. Ідентифікаційні коди мають бути унікальними в одному файлі та обов’язково повторюватись у різних файлах.

Вміст файлу “Workers.txt“:

1; Ivanov I.I.; Bachelor; Programmer Engineer; 1900
2; Petrov P.P.; Master; Team Lead; 1950
3; Sidorov S.S.; Super Master; Software Architect; 1990
4; Johnson J.J.; Super Bachelor; HTML-coder; 1997
5; Nicolson J.J.; Bachelor; DevOps engineer; 1992

Вміст файлу “Salary.txt“:

1; 23550; 26580
2; 26800; 28390
3; 24660; 27777
4; 35880; 44444
5; 55555; 39938

  ⇑

Виконання

1. Створення проекту типу Windows Forms Application

Створити проект як Windows Forms Application.

Приклад створення проекту типу Windows Forms Application наведено тут.

Зберегти файли проекту в деякий каталог. Скопіювати файли Workers.txt та Salary.txt в той самий каталог, де має бути виконавчий (.exe) файл програми.

  ⇑

2. Розробка основної форми програми

Побудувати форму як показано на рисунку 1. На формі розміщуються такі елементи управління:

  • три елементи управління типу Label. Автоматично створюється три об’єкти (екземпляри класів) з іменами label1, label2, label3;
  • три елементи управління типу Button. Автоматично створюється три об’єкти з іменами button1, button2, button3;
  • три елементи управління типу ListBox. Автоматично створюється три об’єкти з іменами listBox1, listBox2, listBox3.

C#. Шаблон Windows Forms Application. Основна форма програми

Рис. 1. Основна форма програми

  ⇑

3. Налаштування елементів управління форми

Налаштувати такі елементи управління на формі:

  • виділити елемент управління label1 (з допомогою мишки). В елементі управління label1 властивість Text = “Файл Workers.txt“;
  • в елементі управління label2 властивість Text = “Файл Salary.txt“;
  • в елементі управління label3 властивість Text = “Результат“;
  • в елементі управління button1 властивість Text = “Завдання 1“;
  • в елементі управління button2 властивість Text = “Завдання 2“;
  • в елементі управління button3 властивість Text = “Завдання 3“.

Після налаштування форма матиме вигляд, як показано на рисунку 2.

C#. Додаток типу Windows Forms Application. Форма після налаштування елементів управління

Рис. 2. Форма після налаштування елементів управління

  ⇑

4. Підключення простору імен System.Linq.

Щоб використовувати запити на мові LINQ потрібно підключити простір імен System.Linq. Як правило, простір імен System.Linq підключається автоматично при створенні додатку типу Windows Forms Application.

У файлі Form1.cs рядок підключення має вигляд:

using System.Linq;

  ⇑

5. Побудова внутрішніх структур даних, що відповідають файлам “Workers.txt” та “Salary.txt“.

Дані, що відповідають одному рядку файлу Workers.txt доцільно представити у вигляді структури Workers, лістинг якої наступний:

struct Workers
{
    public string code;       // ідентифікаційний код
    public string name;       // прізвище та ініціали
    public string education;  // вид освіти
    public string profession; // фах
    public int year;          // рік народження
}

Так само дані, що відповідають одному рядку файлу Salary.txt доцільно представити у структурі Salary:

struct Salary
{
    public string code;   // ідентифікаційний код
    public float salary1; // зарплата за 1 півріччя
    public float salary2; // зарплата за 2 півріччя
}

Оскільки, рядків у файлах може бути багато, то всі дані можна розмістити у вигляді узагальнених динамічних масивів типу List<T>.

Для структур Workers та Salary динамічні масиви мають такий опис:

List<Workers> lw = null; // список структур типу Workers
List<Salary> ls = null; // список структур типу Salary

Після введення структур, лістинг класу Form1 має вигляд:

...

public partial class Form1 : Form
{
    struct Workers
    {
        public string code; // ідентифікаційний код
        public string name; // прізвище та ініціали
        public string education; // вид освіти
        public string profession; // фах
        public int year; // рік народження
    }

    struct Salary
    {
        public string code; // ідентифікаційний код
        public float salary1; // зарплата за 1 півріччя
        public float salary2; // зарплата за 2 півріччя
    }

    List<Workers> lw = null; // список структур типу Workers
    List<Salary> ls = null; // список структур типу Salary

    public Form1()
    {
        InitializeComponent();
    }
}

...

  ⇑

6. Підключення простору імен System.IO

Для читання даних з файлів у програмі буде використано можливості класу StreamReader з бібліотеки класів .NET Framework. Тому, для використання методів цих класів на початку файлу “Form1.cs” потрібно додати рядок:

using System.IO;

  ⇑

7. Створення методів Read_Workers() та Read_Salary() для читання даних з файлів “Workers.txt” та “Salary.txt“

Для читання даних з файлів “Workers.txt” та “Salary.txt” потрібно ввести два методи:

  • Read_Workers();
  • Read_Salary().


Реалізація цих методів розміщується у класі форми Form1. Лістинг методу Read_Workers() наступний:

// читання даних з файлу "Workers.txt"
public void Read_Workers()
{
    // створити об'єкт класу StreamReader, який відповідає файлу "Workers.txt"
    StreamReader sr = File.OpenText("Workers.txt");
    string[] fields; // змінна, що відповідає полям структури Workers
    string line = null;
    Workers w;

    // прочитати рядок
    line = sr.ReadLine();

    while (line != null)
    {
        // розбити рядок на підрядки - розділювачем є символ ';'
        fields = line.Split(';');

        // побудова структури типу Workers
        w.code = fields[0];
        w.name = fields[1];
        w.education = fields[2];
        w.profession = fields[3];
        w.year = Int32.Parse(fields[4]);

        // додавання структури типу Workers у список List<Workers>
        lw.Add(w);

        // додавання рядка в список listBox1
        listBox1.Items.Add(line);
        // прочитати наступний рядок
        line = sr.ReadLine();
    }
}

Метод Read_Workers() читає дані з файлу “Workers.txt” та записує їх у:

  • динамічний масив lw типу List<Workers>;
  • елемент управління listBox1 для відображення на формі.

Лістинг методу Read_Salary() наступний:

// читання даних з файлу "Salary.txt"
public void Read_Salary()
{
    // створити об'єкт класу StreamReader, який відповідає файлу "Salary.txt"
    StreamReader sr = File.OpenText("Salary.txt");
    string[] fields; // змінна, що відповідає полям структури Workers
    string line = null;
    Salary s;

    // прочитати рядок
    line = sr.ReadLine();

    while (line != null)
    {
        // розбити рядок на підрядки - розділювачем є символ ';'
        fields = line.Split(';');

        // побудова структури типу Salary
        s.code = fields[0];
        s.salary1 = (float)Convert.ToDouble(fields[1]);
        s.salary2 = (float)Double.Parse(fields[2]);

        // додавання структури типу Salary у список List<Salary>
        ls.Add(s);

        // додавання рядка в список listBox2
        listBox2.Items.Add(line);

        // прочитати наступний рядок
        line = sr.ReadLine();
    }
}

Метод Read_Salary() читає дані з файлу “Salary.txt” та записує їх у:

  • динамічний масив ls типу List<Salary>;
  • елемент управління listBox2 для відображення на формі.

  ⇑

8. Програмування конструктора Form1() основної форми програми. Читання даних з файлів.

Після запуску програми на виконання дані з файлів повинні бути автоматично завантажені в елементи управління listBox1 та listBox2.

Тому, потрібно в конструктор класу Form1() додати методи Read_Workers() та Read_Salary().

Також у конструктор класу Form1() додається виділення пам’яті для динамічних масивів lw та ls.

Загальний лістинг конструктора основної форми має вигляд:

public Form1()
{
    InitializeComponent();

    // виділення пам'яті для списків
    lw = new List<Workers>();
    ls = new List<Salary>();

    // очистити компоненти типу ListBox
    listBox1.Items.Clear();
    listBox2.Items.Clear();
    listBox3.Items.Clear();

    // прочитати дані з файлу "Workers.txt"
    Read_Workers();
    Read_Salary();
}

  ⇑

9. Програмування події кліку на кнопці “Завдання 1“.

Виконання пункту 1 завдання реалізується з допомогою кліку на кнопці “Завдання 1“. У результаті буде викликано відповідний обробник події.

Приклад програмування події кліку на кнопці у додатку типу Windows Forms Application наведено тут.

Лістинг обробника події кліку на кнопці “Завдання 1“.

// Прізвища та ініціали співробітників вище 35 років
private void button1_Click(object sender, EventArgs e)
{
    // запит з іменем names
    var names = from nm in lw
                where nm.year < (2016-35)
                select nm.name;

    listBox3.Items.Clear(); // очистити список

    // додати в список listBox3 результат запиту names
    foreach (string s in names)
        listBox3.Items.Add(s);
}

У наведеному лістингу формується запит на мові LINQ. Цей запит має ім’я names:

var names = from nm in lw
            where nm.year < (2016-35)
            select nm.name;

Запит починається з оператора from. В операторі from задається змінна діапазону, яка має ім’я nm. Джерелом даних в операторі from є динамічний масив lw типу Workers.

Наступним йде оператор where, який означає умову, якій повинен задовольняти елемент в джерелі даних, щоб його можна було б отримати за запитом.

Запит завершується оператором select. В операторі select задається, що саме повинно виводитись за запитом. У даному прикладі вибирається поле name структури Workers (прізвища та ініціали співробітників вище 35 років).

Для виконання запиту в програмі використовується цикл foreach.

foreach (string s in names)
    listBox3.Items.Add(s);

Оскільки, результатом LINQ запиту є тип string, то у циклі описується змінна s типу string.

  ⇑

10. Програмування події кліку на кнопці “Завдання 2“.

Обробник події кліку на кнопці “Завдання 2” має вигляд:

// ідентифікаційний код співробітника з найбільшою зарплатою
// за ІІ півріччя
private void button2_Click(object sender, EventArgs e)
{
    // запит max_salary
    var max_salary = (from ms in ls
        select ms.salary2).Max(); // метод Max() повертає максимальне знач.
    listBox3.Items.Clear(); 
    listBox3.Items.Add(max_salary);
}

У цьому LINQ-запиті використовується метод Max(), що знаходить максимальне значення в переліку (списку). Цей перелік формується у запиті з іменем max_salary.

  ⇑

11. Програмування події кліку на кнопці “Завдання 3“.

Лістинг обробника події кліку на кнопці “Завдання 3” наступний:

// Прізвища, ініціали та вид освіти тих співробітників,
// зарплата яких за рік нижче середньої за рік
private void button3_Click(object sender, EventArgs e)
{
    var result = from w in lw
                 from sl in ls
                 let avg = (from s in ls
                            select (s.salary1 + s.salary2)).Average()
                 where ((sl.salary1 + sl.salary2) < avg) && (w.code == sl.code)
                 select w.name +" - " + w.profession;

    listBox3.Items.Clear();

    foreach (string s in result)
        listBox3.Items.Add(s);
}

У цьому обробнику формується запит з іменем result.

У запиті потрібно здійснити вибірку з двох джерел даних. У нашому випадку це джерела:

  • динамічний масив lw типу List<Workers>. З цього масиву вибираються прізвище співробітника та його вид освіти;
  • динамічний масив ls типу List<Salary>. З цього масиву вибираються зарплати за перше та друге півріччя.

Для того, щоб здійснити вибірку з двох джерел даних, запит містить два вкладені оператори from. Для того, щоб вибирати дані, що відповідають одному ідентифікаційному коду, в запиті (в операторі where) використовується рядок порівняння (w.code==sl.code):

where (...) && (w.code == sl.code)

Щоб порахувати середнє арифметичне використовується метод Average() середовища .NET Framework. Саме середнє арифметичне зберігається у змінній avg, що введена в запит з допомогою оператора let:

...
let avg = (from s in ls
    select (s.salary1 + s.salary2)).Average()
...

  ⇑

12. Запуск програми на виконання

Після програмування усіх обробників подій можна запускати програму на виконання.

  ⇑