en

Приклади коду з урахуванням особливостей C# 9 і C# 10

1 Обробка даних про книги на книжковій полиці (лабораторна робота № 1)

Єдине істотне доповнення, пов'язане з перевіркою потенційно можливого значення null, доцільно зробити в інструкції, в якій рядок вводиться з клавіатури. Замість інструкції

string sequence = Console.ReadLine();

слід розташувати таку:

string sequence = Console.ReadLine() ?? "";

Це унеможливить присвоєння змінній sequence значення null.

2 Ієрархія книжкових полиць (лабораторна робота № 2)

З урахуванням особливостей C# 9 і C# 10 можна модифікувати код програми в такий спосіб:

using System;

namespace LabSecond
{
    // Структура для опису автора
    public struct Author
    {
        public string Surname, Name;

        //Перевизначення еквівалентності
        public override bool Equals(object? obj)
        {
            if (obj == null)
            {
                return false;
            }
            Author author = (Author)obj;
            return author.Surname == Surname && author.Name == Name;
        }

        // Визначення представлення у вигляді рядку:
        public override string ToString()
        {
            return Name + " " + Surname;
        }

        // Визначається у парі з Equals()
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public static bool operator ==(Author left, Author right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Author left, Author right)
        {
            return !(left == right);
        }
    }

    // Книжка 
    public class Book
    {
        public string Title { get; set; }
        public int Year { get; set; }
        public Author[] Authors { get; set; }

        // Конструктор
        public Book(string title, int year, params Author[] authors)
        {
            Title = title;
            Year = year;
            Authors = authors;
        }

        // Визначення представлення у вигляді рядку
        // string.Format() забезпечує форматування, аналогічне Console.WriteLine()
        public override string ToString()
        {
            string s = string.Format("Назва: \"{0}\". Рiк видання: {1}", Title, Year);
            s += "\n" + "   Автор(и):";
            for (int i = 0; i < Authors.Length; i++)
            {
                s += string.Format("      {0}", Authors[i]);
                if (i < Authors.Length - 1)
                {
                    s += ",";
                }
                else
                {
                    s += "\n";
                }
            }
            return s;
        }

        // Перевизначення еквівалентності
        public override bool Equals(object? obj)
        {
            if (obj is Book b)
            {
                if (b.Authors.Length != Authors.Length)
                {
                    return false;
                }
                for (int i = 0; i < Authors.Length; i++)
                {
                    if (!b.Authors[i].Equals(Authors[i]))
                    {
                        return false;
                    }
                }
                return b.Title == Title && b.Year == Year;
            }
            return false;
        }

        // Визначається у парі з Equals()
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

    }

    // Книжкова полиця
    public class Bookshelf
    {
        public Book[] Books { get; set; }

        // Конструктор
        public Bookshelf(params Book[] books)
        {
            Books = books;
        }

        // Індексатор
        public Book this[int index]
        {
            get { return Books[index]; }
            set { Books[index] = value; }
        }

        // Визначення представлення у вигляді рядку
        public override string ToString()
        {
            string result = "";
            foreach (Book book in Books)
            {
                result += book;
            }
            return result;
        }

        // Пошук книжки з певною послідовністю літер
        public Book[] ContainsCharacters(string characters)
        {
            Book[] found = Array.Empty<Book>();
            foreach (Book book in Books)
            {
                if (book.Title.Contains(characters))
                {
                    // Додаємо новий елемент до масиву:
                    Array.Resize(ref found, found.Length + 1);
                    found[^1] = book;
                }
            }
            return found;
        }

        // Додавання книжки
        public void Add(Book book)
        {
            Book[] books = Books;
            Array.Resize(ref books, Books.Length + 1);
            Books = books;
            Books[^1] = book;
        }

        // Видалення книжки зі вказаними даними
        public void Remove(Book book)
        {
            int i, k;
            Book[] newBooks = new Book[Books.Length];
            for (i = 0, k = 0; i < Books.Length; i++, k++)
            {
                if (Books[i].Equals(book))
                {
                    k--;
                }
                else
                {
                    newBooks[k] = Books[i];
                }
            }
            if (i > k)
            {
                Array.Resize(ref newBooks, Books.Length - 1);
            }
            Books = newBooks;
        }

        // Перевантажений оператор додавання книжки
        public static Bookshelf operator +(Bookshelf bookshelf, Book book)
        {
            Bookshelf newBookshelf = new(bookshelf.Books);
            newBookshelf.Add(book);
            return newBookshelf;
        }

        // Перевантажений оператор видалення книжки
        public static Bookshelf operator -(Bookshelf bookshelf, Book book)
        {
            Bookshelf newBookshelf = new(bookshelf.Books);
            newBookshelf.Remove(book);
            return newBookshelf;
        }
    }

    // Книжкова полиця з назвою
    public class TitledBookshelf : Bookshelf
    {
        public string Title { get; set; }

        public TitledBookshelf(string title, params Book[] books) : base(books)
        {
            Title = title;
        }

        // Визначення представлення у вигляді рядку
        public override string ToString()
        {
            return Title + "\n" + base.ToString();
        }

        // Перевантажений оператор додавання книжки
        public static TitledBookshelf operator +(TitledBookshelf titled, Book book)
        {
            TitledBookshelf newBookshelf = new(titled.Title, titled.Books);
            newBookshelf.Add(book);
            return newBookshelf;
        }

        // Перевантажений оператор видалення книжки
        public static TitledBookshelf operator -(TitledBookshelf titled, Book book)
        {
            TitledBookshelf newBookshelf = new(titled.Title, titled.Books);
            newBookshelf.Remove(book);
            return newBookshelf;
        }
    }

    class Program
    {
        static void Main()
        {
            // Створюємо порожню полицю:
            Bookshelf bookshelf = new();

            // Додаємо книжки
            bookshelf += new Book("The UML User Guide", 1999,
                                  new Author() { Name = "Grady", Surname = "Booch" },
                                  new Author() { Name = "James", Surname = "Rumbaugh" },
                                  new Author() { Name = "Ivar", Surname = "Jacobson" });
            bookshelf += new Book(@"Об'єктно-орiєнтоване моделювання програмних систем", 2007,
                                  new Author() { Name = "Iгор", Surname = "Дудзяний" });
            bookshelf += new Book("Thinking in Java", 2005,
                                  new Author() { Name = "Bruce", Surname = "Eckel" });

            // Виводимо дані на екран:
            Console.WriteLine(bookshelf);
            Console.WriteLine();

            // Шукаємо книжки з певною послідовністю літер:
            Console.WriteLine("Уведiть послiдовнiсть лiтер:");
            string sequence = Console.ReadLine() ?? "";
            Bookshelf newBookshelf = new(bookshelf.ContainsCharacters(sequence));
            // Виводимо результат на екран:
            Console.WriteLine("Знайденi книжки:");
            Console.WriteLine(newBookshelf);
            Console.WriteLine();

            // Видаляємо книжку про Java
            Book javaBook = bookshelf[2]; // індексатор
            bookshelf -= javaBook;
            Console.WriteLine("Пiсля видалення книжки:");
            Console.WriteLine(bookshelf);
            Console.WriteLine();

            // Створюємо нову полицю
            TitledBookshelf titledBookshelf = new("Java");
            titledBookshelf += javaBook;
            Console.WriteLine("Нова полиця:");
            Console.WriteLine(titledBookshelf);
        }
    }
}

У коді застосовано механізм зіставлення зі зразком, додатковий контроль null-сумісних типів, нові можливості створення нових об'єктів new(), перевантаження операцій == та !=, а також механізм отримання індексів через використання типу Index.

3 Створення ієрархії узагальнених класів (лабораторна робота № 3)

У прикладі цієї лабораторної роботи окремо створюється бібліотека класів і консольний застосунок. У бібліотеці класів розташовуємо такий код:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;

namespace BookshelfLib
{
    // Структура для опису автора
    public struct Author
    {
        [System.Xml.Serialization.XmlAttributeAttribute()]
        public string Surname { get; set; }

        [System.Xml.Serialization.XmlAttributeAttribute()]
        public string Name { get; set; }

        // Перевизначення еквівалентності
        public override bool Equals(object? obj)
        {
            if (obj == null)
            {
                return false;
            }
            Author author = (Author)obj;
            return author.Surname == Surname && author.Name == Name;
        }

        // Визначення представлення у вигляді рядку:
        public override string ToString()
        {
            return Name + " " + Surname;
        }

        // Визначається у парі з Equals()
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public static bool operator ==(Author left, Author right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Author left, Author right)
        {
            return !(left == right);
        }
    }

    // Книжка 
    public class Book<TAuthor>
    {
        [System.Xml.Serialization.XmlAttributeAttribute()]
        public string Title { get; set; }

        [System.Xml.Serialization.XmlAttributeAttribute()]
        public int Year { get; set; }

        public List<TAuthor> Authors { get; set; }

        // Конструктори
        public Book()
        {
            Title = "";
            Authors = new List<TAuthor>();
        }

        public Book(string title, int year, params TAuthor[] authors)
        {
            Title = title;
            Year = year;
            Authors = new List<TAuthor>(authors);
        }

        // Визначення представлення у вигляді рядку
        override public string ToString()
        {
            string s = string.Format("Назва: \"{0}\". Рiк видання: {1}", Title, Year);
            s += "\n" + "   Автор(и):";
            for (int i = 0; i < Authors.Count; i++)
            {
                s += string.Format("      {0}", Authors[i]);
                if (i < Authors.Count - 1)
                {
                    s += ",";
                }
                else
                {
                    s += "\n";
                }
            }
            return s;
        }

        // Перевизначення еквівалентності
        public override bool Equals(object? obj)
        {
            if (obj is Book<TAuthor> b)
            {
                if (b.Authors.Count != Authors.Count)
                {
                    return false;
                }
                for (int i = 0; i < Authors.Count; i++)
                {
                    if (!b.Authors[i].Equals(Authors[i]))
                    {
                        return false;
                    }
                }
                return b.Title == Title && b.Year == Year;
            }
            return false;
        }

        // Визначається у парі з Equals()
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

    }

    // Книжкова полиця
    public class Bookshelf<TAuthor>
    {
        public List<Book<TAuthor>> Books { get; set; }

        // Конструктор
        public Bookshelf(params Book<TAuthor>[] books)
        {
            Books = new List<Book<TAuthor>>(books);
        }

        // Індексатор
        public Book<TAuthor> this[int index]
        {
            get { return Books[index]; }
            set { Books[index] = value; }
        }

        // Визначення представлення у вигляді рядку
        override public string ToString()
        {
            string result = "";
            foreach (Book<TAuthor> book in Books)
            {
                result += book;
            }
            return result;
        }

        // Пошук книжки з певною послідовністю літер
        public List<Book<TAuthor>> ContainsCharacters(string characters)
        {
            List<Book<TAuthor>> found = new();
            foreach (Book<TAuthor> book in Books)
            {
                if (book.Title.Contains(characters))
                {
                    // Додаємо новий елемент до списку:
                    found.Add(book);
                }
            }
            return found;
        }

        // Додавання книжки
        public void Add(Book<TAuthor> book)
        {
            Books.Add(book);
        }

        // Видалення книжки зі вказаними даними
        public void Remove(Book<TAuthor> book)
        {
            Books.Remove(book);
        }

        // Читання книжок за допомогою механізму десеріалізації
        public bool ReadBooks(string fileName)
        {
            XmlSerializer deserializer = new(typeof(List<Book<TAuthor>>));
            using TextReader textReader = new StreamReader(fileName);
            var data = deserializer.Deserialize(textReader);
            if (data == null)
            {
                return false;
            }
            Books = (List<Book<TAuthor>>)data;
            return true;
        }

        // Запис книжок за допомогою механізму серіалізації
        public void WriteBooks(string fileName)
        {
            XmlSerializer serializer = new(typeof(List<Book<TAuthor>>));
            using TextWriter textWriter = new StreamWriter(fileName);
            serializer.Serialize(textWriter, Books);
        }

        // Вкладений клас для порівняння книжок за алфавітом назв
        class CompareByTitle : IComparer<Book<TAuthor>>
        {
            public int Compare(Book<TAuthor>? b1, Book<TAuthor>? b2)
            {
                if (b1 == null || b2 == null)
                {
                    return 0;
                }
                return string.Compare(b1.Title, b2.Title);
            }
        }

        // Вкладений клас для порівняння книжок за кількістю авторів
        class CompareByAuthorsCount : IComparer<Book<TAuthor>>
        {
            public int Compare(Book<TAuthor>? b1, Book<TAuthor>? b2)
            {
                if (b1 == null || b2 == null)
                {
                    return 0;
                }
                return b1.Authors.Count < b2.Authors.Count ? -1 :
                       (b1.Authors.Count == b2.Authors.Count ? 0 : 1);
            }
        }

        // Сортування книжок за алфавітом назв
        public void SortByTitle()
        {
            Books.Sort(new CompareByTitle());
        }

        // Сортування книжок за кількістю авторів
        public void SortByAuthorsCount()
        {
            Books.Sort(new CompareByAuthorsCount());
        }

        // Перевантажений оператор додавання книжки
        public static Bookshelf<TAuthor> operator +(Bookshelf<TAuthor> bookshelf, Book<TAuthor> book)
        {
            Bookshelf<TAuthor> newShelf = new() { Books = bookshelf.Books };
            newShelf.Add(book);
            return newShelf;
        }

        // Перевантажений оператор видалення книжки
        public static Bookshelf<TAuthor> operator -(Bookshelf<TAuthor> bookshelf, Book<TAuthor> book)
        {
            Bookshelf<TAuthor> newShelf = new() { Books = bookshelf.Books };
            newShelf.Remove(book);
            return newShelf;
        }
    }

    // Книжкова полиця з назвою
    public class TitledBookshelf<TAuthor> : Bookshelf<TAuthor>
    {
        public string Title { get; set; }

        // Конструктор з параметрами
        public TitledBookshelf(string title, params Book<TAuthor>[] books)
                  : base(books)
        {
            Title = title;
        }

        // Визначення представлення у вигляді рядку
        override public string ToString()
        {
            return Title + "\n" + base.ToString();
        }

        // Перевантажений оператор додавання книжки
        public static TitledBookshelf<TAuthor> operator +(TitledBookshelf<TAuthor> titled, Book<TAuthor> book)
        {
            TitledBookshelf<TAuthor> newShelf = new(titled.Title)
            {
                Books = titled.Books
            };
            newShelf.Add(book);
            return newShelf;
        }

        // Перевантажений оператор видалення книжки
        public static TitledBookshelf<TAuthor> operator -(TitledBookshelf<TAuthor> titled, Book<TAuthor> book)
        {
            TitledBookshelf<TAuthor> newShelf = new(titled.Title)
            {
                Books = titled.Books
            };
            newShelf.Remove(book);
            return newShelf;
        }
    }
}

Код класу, який представляє консольний застосунок, буде таким:

using BookshelfLib;

namespace BookshelfApp
{
    class Program
    {
        static void Main()
        {
            // Створюємо порожню полицю:
            Bookshelf<Author> bookshelf = new();

            // Додаємо книжки
            bookshelf += new Book<Author>("The UML User Guide", 1999,
                                          new Author() { Name = "Grady", Surname = "Booch" },
                                          new Author() { Name = "James", Surname = "Rumbaugh" },
                                          new Author() { Name = "Ivar", Surname = "Jacobson" });
            bookshelf += new Book<Author>(@"Об'єктно-орiєнтоване моделювання програмних систем", 2007,
                                          new Author() { Name = "Iгор", Surname = "Дудзяний" });
            bookshelf += new Book<Author>("Thinking in Java", 2005,
                                          new Author() { Name = "Bruce", Surname = "Eckel" });

            // Виводимо дані на екран:
            Console.WriteLine(bookshelf);
            Console.WriteLine();

            // Шукаємо книжки з певною послідовністю літер:
            Console.WriteLine("Уведiть послiдовнiсть лiтер:");
            string sequence = Console.ReadLine() ?? "";
            Bookshelf<Author> newBookshelf = new()
            {
                Books = bookshelf.ContainsCharacters(sequence)
            };

            // Виводимо результат на екран:
            Console.WriteLine("Знайденi книжки:");
            Console.WriteLine(newBookshelf);
            Console.WriteLine();

            try
            {
                // Зберігаємо дані про книжки:
                bookshelf.WriteBooks("Bookshelf.xml");

                // Здійснюємо сортування за назвами та зберігаємо у файлі:
                bookshelf.SortByTitle();
                Console.WriteLine("За назвами:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();
                bookshelf.WriteBooks("ByTitle.xml");

                // Здійснюємо сортування за кількістю авторів та зберігаємо у файлі:
                bookshelf.SortByAuthorsCount();
                Console.WriteLine("За кiлькiстю авторiв:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();
                bookshelf.WriteBooks("ByAuthorsCount.xml");

                // Відтворюємо першу полицю в початковому варіанті
                bookshelf.ReadBooks("Bookshelf.xml");
                Console.WriteLine("Початковий стан:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();

                // Видаляємо книжку про Java
                Book<Author> javaBook = bookshelf[2]; // індексатор
                bookshelf -= javaBook;
                Console.WriteLine("Пiсля видалення книжки:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();

                // Створюємо нову полицю. Для зберігання даних про автора використовуємо рядок
                TitledBookshelf<string> titledBookshelf = new("Java");
                titledBookshelf += new Book<string>("Thinking in Java", 2005, "Bruce Eckel");
                Console.WriteLine("Полиця з книжками з мови Java:");
                Console.WriteLine(titledBookshelf);
                titledBookshelf.WriteBooks("JavaBooks.xml");

            }
            catch (Exception ex)
            {
                Console.WriteLine("------------Виняток:------------");
                Console.WriteLine(ex.GetType());
                Console.WriteLine("-------------Змсiт:-------------");
                Console.WriteLine(ex.Message);
                Console.WriteLine("-------Трасування  стеку:-------");
                Console.WriteLine(ex.StackTrace);
            }
        }
    }
}

Як і в коді попередньої роботи, застосовано механізм зіставлення зі зразком, додатковий контроль null-сумісних типів, нові можливості створення нових об'єктів new(), перевантаження операцій == та != для типів-значень. Також видалені зайві using-директиви.

4 Створення GUI-застосунку для обробки даних про книжки (лабораторна робота № 4)

У прикладі цієї лабораторної роботи доповнюється бібліотека класів. Сирцевий код класу BookshelfWithLINQ буде таким:

// BookshelfWithLINQ.cs

namespace BookshelfLib
{
    public class BookshelfWithLINQ<TAuthor> : Bookshelf<TAuthor>
    {
        // Конструктор
        public BookshelfWithLINQ(params Book<TAuthor>[] books) : base(books)
        {
        }

        public new List<Book<TAuthor>> ContainsCharacters(string characters)
        {
            var found = from book in Books
                        where book.Title.Contains(characters)
                        select book;

            // повертаємо результат пошуку found,
            // створивши на його основі новий список типу List<Book<TAuthor>>
            // для забезпечення відповідності типів:
            return new List<Book<TAuthor>>(found);
        }

        // Сортування за алфавітом назв:
        public new void SortByTitle()
        {
            Books = new List<Book<TAuthor>>(
                from book in Books
                orderby book.Title
                select book);
        }

        // Сортування за кількістю авторів:
        public new void SortByAuthorsCount()
        {
            Books = new List<Book<TAuthor>>(
                from book in Books
                orderby book.Authors.Count
                select book);
        }

        // Перевантажений оператор додавання книжки
        public static BookshelfWithLINQ<TAuthor> operator +
            (BookshelfWithLINQ<TAuthor> bookshelf, Book<TAuthor> newBook)
        {
            var newBooks = new List<Book<TAuthor>>(bookshelf.Books) { newBook };
            return new BookshelfWithLINQ<TAuthor>() { Books = newBooks };
        }

        // Перевантажений оператор видалення книжки.
        // Використання LINQ – суто ілюстративне
        public static BookshelfWithLINQ<TAuthor> operator -
            (BookshelfWithLINQ<TAuthor> bookshelf, Book<TAuthor> oldBook)
        {
            var newBooks = new List<Book<TAuthor>>(
                from book in bookshelf.Books
                where !book.Equals(oldBook)
                select book);
            return new BookshelfWithLINQ<TAuthor>() { Books = newBooks };
        }

    }
}

Відповідний консольний застосунок:

using BookshelfLib;

namespace BookshelfWithLINQApp
{
    class Program
    {
        static void Main()
        {
            // Створюємо порожню полицю:
            BookshelfWithLINQ<Author> bookshelf = new();


            // Додаємо книжки
            bookshelf += new Book<Author>("The UML User Guide", 1999,
                                          new Author() { Name = "Grady", Surname = "Booch" },
                                          new Author() { Name = "James", Surname = "Rumbaugh" },
                                          new Author() { Name = "Ivar", Surname = "Jacobson" });
            bookshelf += new Book<Author>(@"Об'єктно-орiєнтоване моделювання програмних систем", 2007,
                                          new Author() { Name = "Iгор", Surname = "Дудзяний" });
            bookshelf += new Book<Author>("Thinking in Java", 2005,
                                          new Author() { Name = "Bruce", Surname = "Eckel" });

            // Виводимо дані на екран:
            Console.WriteLine(bookshelf);
            Console.WriteLine();

            // Шукаємо книжки з певною послідовністю літер:
            Console.WriteLine("Уведiть послiдовнiсть лiтер:");
            string sequence = Console.ReadLine() ?? "";
            BookshelfWithLINQ<Author> newBookshelf = new() { Books = bookshelf.ContainsCharacters(sequence) };


            // Виводимо результат на екран:
            Console.WriteLine("Знайденi книжки:");
            Console.WriteLine(newBookshelf);
            Console.WriteLine();

            try
            {
                // Зберігаємо дані про книжки:
                bookshelf.WriteBooks("Bookshelf.xml");

                // Здійснюємо сортування за назвами та зберігаємо у файлі:
                bookshelf.SortByTitle();
                Console.WriteLine("За назвами:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();
                bookshelf.WriteBooks("ByTitle.xml");

                // Здійснюємо сортування за кількістю авторів та зберігаємо у файлі:
                bookshelf.SortByAuthorsCount();
                Console.WriteLine("За кiлькiстю авторiв:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();
                bookshelf.WriteBooks("ByAuthorsCount.xml");

                // Створюємо нову полицю. Для зберігання даних про автора використовуємо рядок
                TitledBookshelf<string> titledBookshelf = new("Java");
                titledBookshelf += new Book<string>("Thinking in Java", 2005, "Bruce Eckel");
                Console.WriteLine("Полиця з книжками з мови Java:");
                Console.WriteLine(titledBookshelf);
                titledBookshelf.WriteBooks("JavaBooks.xml");

                // Відтворюємо першу полицю в початковому варіанті
                bookshelf.ReadBooks("Bookshelf.xml");
                Console.WriteLine("Початковий стан:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();

                // Видаляємо книжку про Java
                Book<Author> javaBook = bookshelf[2]; // індексатор
                bookshelf -= javaBook;
                Console.WriteLine("Пiсля видалення книжки:");
                Console.WriteLine(bookshelf);
                Console.WriteLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine("------------Виняток:------------");
                Console.WriteLine(ex.GetType());
                Console.WriteLine("-------------Змсiт:-------------");
                Console.WriteLine(ex.Message);
                Console.WriteLine("-------Трасування  стеку:-------");
                Console.WriteLine(ex.StackTrace);
            }
        }
    }
}

Файл з XAML-кодом основного вікна залишається без змін:

<Window x:Class="WpfBookshelfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfBookshelfApp"
        mc:Ignorable="d"
        Title="Книжкова полиця" Height="450" Width="685">
    <DockPanel>
        <Grid Height="54" DockPanel.Dock="Top" VerticalAlignment="Top">
            <Button Content="Відкрити" Height="42" HorizontalAlignment="Left" Margin="5,6,0,0" 
                    Name="ButtonOpen" VerticalAlignment="Top" Width="103" Click="ButtonOpen_Click" />
            <Button Height="42" HorizontalAlignment="Left" Margin="115,6,0,0" 
                    Name="ButtonSortByTitle" VerticalAlignment="Top" Width="103" Click="ButtonSortByTitle_Click">
                <Grid>
                    <TextBlock Text="Сортувати за назвою" TextWrapping="Wrap" TextAlignment="Center" />
                </Grid>
            </Button>
            <Button Height="42" HorizontalAlignment="Left" Margin="225,6,0,0" 
                    Name="ButtonSortByAuthorsCount" VerticalAlignment="Top" Width="103" Click="ButtonSortByAuthorsCount_Click">
                <Grid>
                    <TextBlock Text="Сортувати за кількістю авторів" TextWrapping="Wrap" 
                               TextAlignment="Center" />
                </Grid>
            </Button>
            <TextBox Height="24" HorizontalAlignment="Left" Margin="335,14,0,0" 
                     Name="TextBoxSearch" VerticalAlignment="Top" Width="109" />
            <Button Content="Знайти" Height="42" HorizontalAlignment="Left" Margin="450,6,0,0" 
                    Name="ButtonSearch" VerticalAlignment="Top" Width="103" Click="ButtonSearch_Click" />
            <Button Content="Зберегти" Height="42" HorizontalAlignment="Left" Margin="560,6,0,0" 
                    Name="ButtonSave" VerticalAlignment="Top" Width="103" Click="ButtonSave_Click" />
        </Grid>
        <DataGrid AutoGenerateColumns="False" Height="130" Name="DataGridAuthors" 
                  DockPanel.Dock="Bottom" VerticalAlignment="Bottom">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Ім'я" Width="100" x:Name="ColumnName" />
                <DataGridTextColumn Header="Прізвище" Width="100" x:Name="ColumnSurname" />
            </DataGrid.Columns>
        </DataGrid>
        <DataGrid AutoGenerateColumns="False" Name="DataGridBooks" SelectionChanged="DataGridBooks_SelectionChanged">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Назва" Width="350" x:Name="ColumnTitle" />
                <DataGridTextColumn Header="Рік видання" x:Name="ColumnYear" />
            </DataGrid.Columns>
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Додати рядок" Name="MenuItemAdd" Click="MenuItemAdd_Click" />
                    <MenuItem Header="Видалити рядок" Name="MenuItemRemove" Click="MenuItemRemove_Click" />
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
    </DockPanel>
</Window>

Аналогічно залишаємо без змін файл опису додаткового вікна:

<Window x:Class="WpfBookshelfApp.WindowSearchResults"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfBookshelfApp"
        mc:Ignorable="d"
        Title="Результати пошуку" Height="300" Width="600">
    <Grid>
        <TextBox Name="TextBoxSearchResults" />
    </Grid>
</Window>

Код класів MainWindow і Author буде таким:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using BookshelfLib;

namespace WpfBookshelfApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private BookshelfWithLINQ<Author> bookshelf = new();

        public MainWindow()
        {
            InitializeComponent();

            // Додаємо порожню книжку:
            bookshelf += new Book<Author>()
            {
                Title = "",
                Authors = new List<Author> { new Author() { Name = "", Surname = "" } }
            };
            InitGrid();
        }

        void InitGrid()
        {
            // Зв'язуємо таблицю DataGridBooks зі списком книг:
            DataGridBooks.ItemsSource = bookshelf.Books;
            DataGridBooks.CanUserAddRows = false;

            // Вказуємо, які колонки зв'язані з якими властивостями:
            ColumnTitle.Binding = new Binding("Title");
            ColumnYear.Binding = new Binding("Year");

            // Показуємо авторів для обраної книжки:
            ShowAuthors(DataGridBooks.Items.IndexOf(DataGridBooks.SelectedItem));
        }

        private void ShowAuthors(int index)
        {
            // Якщо індекс хибний, встановлюємо індекс 0
            if (index < 0 || index >= bookshelf.Books.Count)
            {
                index = 0;
            }

            // Зв'язуємо таблицю DataGridAuthors зі списком авторів:
            DataGridAuthors.ItemsSource = bookshelf.Books[index].Authors;
            DataGridAuthors.CanUserAddRows = false;

            // Вказуємо, які колонки зв'язані з якими властивостями:
            ColumnName.Binding = new Binding("Name");
            ColumnSurname.Binding = new Binding("Surname");
        }

        private void DataGridBooks_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // Відображаємо авторів книжки, орбаної в DataGridBooks:
            ShowAuthors(DataGridBooks.Items.IndexOf(DataGridBooks.SelectedItem));
        }

        private void MenuItemAdd_Click(object sender, RoutedEventArgs e)
        {
            // Підтверджуємо зміни даних у таблиці:
            DataGridBooks.CommitEdit();
            // Додаємо порожню книжку:
            bookshelf += new Book<Author>()
            {
                Title = "",
                Authors = new List<Author> { new Author() { Name = "", Surname = "" } }
            };
            InitGrid();
        }

        private void MenuItemRemove_Click(object sender, RoutedEventArgs e)
        {
            // Визначаємо індекс активного рядку:
            int index = DataGridBooks.SelectedIndex;
            // Підтверджуємо зміни даних у таблиці:
            DataGridBooks.CommitEdit();
            // Видаляємо активний рядок:
            bookshelf.Books.RemoveAt(index);
            // Якщо видалили всі рядки, додаємо новий порожній:
            if (bookshelf.Books.Count == 0)
            {
                bookshelf = new BookshelfWithLINQ<Author>();
                bookshelf += new Book<Author>("", 0, new Author());
                InitGrid();
            }
            DataGridBooks.ItemsSource = null;
            InitGrid();
        }

        private void ButtonOpen_Click(object sender, RoutedEventArgs e)
        {
            // Створюємо діалогове вікно та налагоджуємо його властивості:
            Microsoft.Win32.OpenFileDialog dlg = new();
            dlg.InitialDirectory = System.AppDomain.CurrentDomain.BaseDirectory; // поточна тека
            dlg.DefaultExt = ".xml";
            dlg.Filter = "Файли XML (*.xml)|*.xml|Усі файли (*.*)|*.*";
            if (dlg.ShowDialog() == true)
            {
                try
                {
                    bookshelf.ReadBooks(dlg.FileName);
                }
                catch (Exception)
                {
                    MessageBox.Show("Помилка читання з файлу");
                }
                InitGrid();
            }
        }

        private void ButtonSave_Click(object sender, RoutedEventArgs e)
        {
            // Створюємо діалогове вікно та налагоджуємо його властивості:
            Microsoft.Win32.SaveFileDialog dlg = new();
            dlg.InitialDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
            dlg.DefaultExt = ".xml";
            dlg.Filter = "Файли XML (*.xml)|*.xml|Усі файли (*.*)|*.*";
            if (dlg.ShowDialog() == true)
            {
                try
                {
                    bookshelf.WriteBooks(dlg.FileName);
                    MessageBox.Show("Файл збережено");
                }
                catch (Exception)
                {
                    MessageBox.Show("Помилка запису в файл");
                }
            }
        }

        private void ButtonSortByTitle_Click(object sender, RoutedEventArgs e)
        {
            bookshelf.SortByTitle();
            InitGrid();
        }

        private void ButtonSortByAuthorsCount_Click(object sender, RoutedEventArgs e)
        {
            bookshelf.SortByAuthorsCount();
            InitGrid();
        }

        private void ButtonSearch_Click(object sender, RoutedEventArgs e)
        {
            string sequence = TextBoxSearch.Text;
            if (sequence.Length == 0)
            {
                return;
            }
            // Знаходимо книжки за ознакою:
            var found = bookshelf.ContainsCharacters(sequence);

            // Формуємо рядок результатів:
            string text = "";
            foreach (var book in found)
            {
                text += book + "\n";
            }
            // Створюємо нове вікно:
            WindowSearchResults windowSearchResults = new();
            windowSearchResults.TextBoxSearchResults.Text = text;
            windowSearchResults.ShowDialog();
        }
    }
    // Клас для опису автора
    public class Author
    {

        [System.Xml.Serialization.XmlAttributeAttribute()]
        public string Surname { get; set; } = "";

        [System.Xml.Serialization.XmlAttributeAttribute()]
        public string Name { get; set; } = "";

        // Перевизначення еквівалентності
        public override bool Equals(object? obj)
        {
            if (obj == null)
            {
                return false;
            }
            Author author = (Author)obj;
            return author.Surname == Surname && author.Name == Name;
        }

        // Визначення представлення у вигляді рядку:
        public override string ToString()
        {
            return Name + " " + Surname;
        }

        // Визначається у парі з Equals()
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}

Застосовано додатковий контроль null-сумісних типів, нові можливості створення нових об'єктів new() та ініціалізації списків і властивостей. Видалені зайві using-директиви.

up