Stack
Открытые конструкторы
Stack
Перегружен. Инициализирует новый экземпляр класса Stack
Открытые свойства
Count
Возвращает число элементов, которое хранится в классе Stack
IsSynchronized
Возвращает значение, определяющее, является ли доступ к классу Stack синхронизированным (потокобезопасным)
SyncRoot
Возвращает объект, который может быть использован для синхронизации доступа к классу Stack
Открытые методы
Clear
Удаляет все объекты из класса Stack
Clone
Создает неполную копию класса Stack
Contains
Определяет, принадлежит ли элемент классу Stack
CopyTo
Копирует элементы класса Stack в существующий одномерный массив класса Array, начиная с указанного индекса массива
GetEnumerator Интерфейс IEnumerator для класса Stack
Peek
Возвращает самый верхний объект класса Stack, но не удаляет его
Pop
Удаляет и возвращает верхний объект класса Stack
Push
Вставляет объект в начало класса Stack
Synchronized Возвращает синхронизированную (потокобезопасную) обертку класса Stack
ToArray
Копирует элементы класса Stack в новый массив
Класс Stack допускает в качестве действительного значение "пустая ссылка", а также допускает наличие повторяющихся элементов.
Ниже приведен пример создания и инициализации класса Stack и способ вывода его значений: using System; using System.Collections; public class SamplesStack { public static void Main()
{
// Creates and initializes a new Stack.
Stack myStack = new Stack(); myStack.Push("Hello"); myStack.Push("world"); myStack.Push("!");
// Displays the properties and values of the Stack.
Console.WriteLine( "myStack" );
Console.WriteLine( "\tCount: {0}", myStack.Count );
Console.Write( "\tValues:" );
PrintValues( myStack );
} public static void PrintValues( IEnumerable myCollection )
{
System.Collections.IEnumerator myEnumerator = myCollection.GetEnumerator(); while ( myEnumerator.MoveNext() )
Console.Write( "\t{0}", myEnumerator.Current );
Console.WriteLine();
}
}
Листинг 12.5.
Результат: myStack
Count: 3
Values: ! world Hello
Перечислитель
Наследует интерфейс IEnumerator, который является основным для всех перечислителей.
Поддерживает простое перемещение по коллекции.
Открытые свойства
Current
Возвращает текущий элемент коллекции
Открытые методы
MoveNext
Перемещает перечислитель на следующий элемент коллекции
Reset
Устанавливает перечислитель в исходное положение перед первым элементом коллекции
Перечислитель позволяет считывать (только считывать!) информацию (данные) из коллекции. Перечислители не используются для изменения содержания коллекции. Для этого применяются специфические методы данной коллекции ( Enqueue, Dequeue, Push,
Pop ).
Вновь созданный перечислитель размещается перед первым элементом коллекции.
Метод Reset возвращает перечислитель обратно в положение перед первым элементом коллекции.
В этом положении обращение к свойству Current приводит к возникновению исключения.
Поэтому необходимо вызвать метод MoveNext, чтобы переместить перечислитель на первый элемент коллекции до считывания значения свойства Current.
Свойство Current не меняет своего значения (возвращает ссылку на один и тот же член коллекции), пока не будет вызван метод MoveNext или Reset.
Метод MoveNext обеспечивает изменение значения свойства Current.
Завершение перемещения по коллекции приводит к установке перечислителя после последнего элемента коллекции.
При этом вызов
метода MoveNext возвращает значение false.
Если последний вызов
метода MoveNext вернул значение false, обращение к свойству Current приводит к возникновению исключения.
Последовательный вызов методов Reset и MoveNext приводит к перемещению перечислителя на первый элемент коллекции.
Перечислитель действителен до тех пор, пока в коллекцию не вносятся изменения. Если в коллекцию вносятся изменения (добавляются или удаляются элементы коллекции), перечислитель становится недействительным, а следующий вызов методов MoveNext или Reset приводит к возникновению исключения InvalidOperationException.
После изменения коллекции в промежутке между вызовом метода MoveNext и новым обращением к свойству Current, свойство Current возвращает текущий элемент коллекции, даже если перечислитель уже недействителен.
Перечислитель не имеет монопольного доступа к коллекции, поэтому
перечисление в коллекции не является потокобезопасной операцией. Даже при синхронизации коллекции другие потоки могут изменить ее, что приводит к созданию исключения при перечислении. Чтобы обеспечить потокобезопасность при перечислении, можно либо заблокировать коллекцию на все время перечисления,
либо перехватывать исключения, которые возникают в результате изменений, внесенных другими потоками.
Практическая работа №13 Использование регулярных выражений
Синтаксис регулярных выражений Рассмотрим вкратце некоторые элементы синтаксиса регулярных выражений:
^: соответствие должно начинаться в начале строки
(например, выражение @"^пр\w*" соответствует слову "привет" в строке "привет мир")
$: конец строки (например, выражение @"\w*ир$" соответствует слову "мир" в строке "привет мир", так как часть "ир" находится в самом конце)
.: знак точки определяет любой одиночный символ
(например, выражение "м.р" соответствует слову "мир" или "мор")
*: предыдущий символ повторяется 0 и более раз
+: предыдущий символ повторяется 1 и более раз
?: предыдущий символ повторяется 0 или 1 раз
\s: соответствует любому пробельному символу
\S:
соответствует любому символу, не являющемуся пробелом
\w: соответствует любому алфавитно-цифровому символу
\W: соответствует любому не алфавитно-цифровому символу
\d: соответствует любой десятичной цифре
\D : соответствует любому символу, не являющемуся десятичной цифрой
Это только небольшая часть элементов. Более подробное описание синтаксиса регулярных выражений можно найти на msdn в статье Элементы языка регулярных выражений — краткий справочник.
Теперь посмотрим на некоторые примеры использования. Возьмем первый пример с скороговоркой "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа" и найдем в ней все слова, где встречается корень "губ":
1 2 string s = "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа";
Regex regex = new Regex(@"\w*губ\w*");
Так как выражение \w* соответствует любой последовательности алфавитно-цифровых символов любой длины, то данное выражение найдет все слова, содержащие корень "губ".
Второй простенький пример - нахождение телефонного номера в формате 111-111-1111:
1 2 string s = "456-435-2318";
Regex regex = new Regex(@"\d{3}-\d{3}-\d{4}");
Если мы точно знаем, сколько определенных символов должно быть, то мы можем явным образом указать их количество в фигурных скобках: \d{3} - то есть в данном случае три цифры.
Мы можем не только задать поиск по определенным типам символов - пробелы, цифры, но и задать конкретные символы, которые должны входить в регулярное выражение.
Например, перепишем пример с номером телефона и явно укажем, какие символы там должны быть:
1 2 string s = "456-435-2318";
Regex regex = new Regex("[0-9]{3}-[0-9]{3}-[0-9]{4}");
В квадратных скобках задается диапазон символов, которые должны в данном месте встречаться. В итоге данный и предыдущий шаблоны телефонного номера будут эквивалентны.
Также можно задать диапазон для алфавитных символов: Regex regex = new Regex("[a- v]{5}"); - данное выражение будет соответствовать любому сочетанию пяти символов, в котором все символы находятся в диапазоне от a до v.
Можно также указать отдельные значения: Regex regex = new Regex(@"[2]*-[0-9]{3}-
\d{4}");. Это
выражение будет соответствовать, например, такому номеру телефона "222-
222-2222" (так как первые числа двойки)
С помощью операции | можно задать альтернативные символы: Regex regex = new
Regex(@"[2\|3]{3}-[0-9]{3}-\d{4}");. То есть первые три цифры могут содержать только двойки или тройки. Такой шаблон будет соответствовать, например, строкам "222-222-
2222" и "323-435-2318". А вот строка "235-435-2318" уже не подпадает под шаблон, так как одной из трех первых цифр является цифра 5.
Итак, у нас такие символы, как *, + и ряд других используются в качестве специальных символов. И возникает вопрос, а что делать, если нам надо найти, строки, где содержится точка, звездочка или какой-то другой специальный символ? В этом случае нам надо просто экранировать эти символы слешем:
1 2
Regex regex = new Regex(@"[2\|3]{3}\.[0-9]{3}\.\d{4}");
// этому выражению будет соответствовать строка "222.222.2222"
Проверка на соответствие строки формату Нередко возникает задача проверить корректность данных, введенных пользователем. Это может быть проверка электронного адреса, номера телефона, Класс Regex предоставляет статический метод
IsMatch, который позволяет проверить входную строку с шаблоном на соответствие:
1 2
3 4
5 6
7 8
9 10 11 12 13 14 15 16 17 string pattern = @"^(?("")(""[^""]+?""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9]{2,17}))$"; while (true)
{
Console.WriteLine("Введите адрес электронной почты"); string email = Console.ReadLine(); if (Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase))
{
Console.WriteLine("Email подтвержден"); break;
} else
{
Console.WriteLine("Некорректный email");
}
}
Переменная pattern задает регулярное выражение для проверки адреса электронной почты.
Данное выражение предлагает нам Microsoft на страницах msdn.
Для проверки соответствия строки шаблону используется метод
IsMatch: Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase). Последний параметр указывает, что регистр можно игнорировать. И если введенная строка соответствует шаблону, то метод возвращает true.
Замена и метод Replace Класс Regex имеет метод Replace,
который позволяет заменить строку, соответствующую регулярному выражению, другой строкой:
1 2
3 4
5 string s = "Мама мыла раму. "; string pattern = @"\s+"; string target = " ";
Regex regex = new Regex(pattern); string result = regex.Replace(s, target);
Данная версия метода Replace принимает два параметра: строку с текстом, где надо выполнить замену, и сама строка замены. Так как в качестве шаблона выбрано выражение "\s+ (то есть наличие одного и более пробелов), метод Replace проходит по всему тексту и заменяет несколько подряд идущих пробелов ординарными.
Практическая работа №14 Операции со списками
Класс List
из пространства имен System.Collections.Generic представляет простейший список однотипных объектов.
Среди его методов можно выделить следующие:
void Add(T item): добавление нового элемента в список
void AddRange(ICollection collection): добавление в список коллекции или массива
int BinarySearch(T item): бинарный поиск элемента в списке. Если элемент найден, то метод возвращает индекс этого элемента в коллекции. При этом список должен быть отсортирован.
int IndexOf(T item): возвращает индекс первого вхождения элемента в списке
void Insert(int index, T item): вставляет элемент item в списке на позицию index
bool Remove(T item): удаляет элемент item из списка, и если удаление прошло успешно, то возвращает true
void RemoveAt(int index): удаление элемента по указанному индексу index
void Sort(): сортировка списка
Посмотрим реализацию списка на примере:
1 2
3 4
5 6
7 8
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using System; using System.Collections.Generic; namespace Collections
{ class Program
{ static void Main(string[] args)
{
List numbers = new List() { 1, 2, 3, 45 }; numbers.Add(6); // добавление элемента numbers.AddRange(new int[] { 7, 8, 9 }); numbers.Insert(0, 666); // вставляем на первое место в списке число 666 numbers.RemoveAt(1); // удаляем второй элемент foreach (int i in numbers)
{
Console.WriteLine(i);
}
List people = new List
(3); people.Add(new Person() { Name = "Том" }); people.Add(new Person() { Name = "Билл" }); foreach (Person p in people)
{
Console.WriteLine(p.Name);
}
Console.ReadLine();
}
} class Person
{ public string Name { get; set; }
40 41
}
}
Здесь у нас создаются два списка: один для объектов типа int, а другой - для объектов Person. В первом случае мы выполняем начальную инициализацию списка: List numbers = new List() { 1, 2, 3, 45 };
Во втором случае мы используем другой конструктор, в который передаем начальную емкость списка: List people = new List
(3);. Указание начальной емкости списка (capacity) позволяет в будущем увеличить производительность и уменьшить издержки на выделение памяти при добавлении элементов. Также начальную емкость можно установить с помощью свойства Capacity, которое имеется у класса List.
Практическая работа №15 Использование основных шаблонов можем выделить несколько основных отношений: наследование, реализация, ассоциация, композиция и агрегация.
Наследование Наследование является базовым принципом ООП и позволяет одному классу (наследнику) унаследовать функционал другого класса (родительского). Нередко отношения наследования еще называют генерализацией или обобщением. Наследование определяет отношение
IS A, то есть "является". Например:
1 2
3 4
5 6
7 8
9 10 class User
{ public int Id { get; set; } public string Name { get; set; }
} class Manager : User
{ public string Company{ get; set; }
}
В
данном случае используется наследование, а объекты класса Manager также
являются и объектами класса User.
С помощью диаграмм UML отношение между классами выражается в незакрашенной стрелочке от класса-наследника к классу-родителю:
Реализация Реализация предполагает определение интерфейса и его реализация в классах. Например, имеется интерфейс IMovable с методом Move, который реализуется в классе Car:
1 2
3 4
5 6
7 8
9 10 11 public interface IMovable
{ void Move();
} public class Car : IMovable
{ public void Move()
{
Console.WriteLine("Машина едет");
}
}
С помощью диаграмм UML отношение реализации также выражается в незакрашенной стрелочке от класса к интерфейсу, только линия теперь пунктирная:
Ассоциация Ассоциация - это отношение, при котором объекты одного типа неким образом связаны с объектами другого типа. Например, объект одного типа содержит или использует объект другого типа. Например, игрок играет в определенной команде:
1 2
3 4
5 6
7 class Team
{
} class Player
{ public Team Team { get; set; }
8
}
Класс Player связан отношением ассоциации с класом Team. На схемах UML ассоциация обозначается в виде обычно стрелки:
Нередко при отношении ассоциации указывается кратность связей. В данном случае единица у Team и звездочка у Player на диаграмме отражает связь 1 ко многим. То есть одна команда будет соответствовать многим игрокам.
Агрегация и композиция являются частными случаями ассоциации.
Композиция Композиция определяет отношение
HAS A, то есть отношение "имеет". Например, в класс автомобиля содержит объект класса электрического двигателя:
1 2
3 4
5 6
7 8
9 10 11 public class ElectricEngine
{ } public class Car
{
ElectricEngine engine; public Car()
{ engine = new ElectricEngine();
}
}
При этом класс автомобиля полностью управляет жизненным циклом объекта двигателя.
При уничтожении объекта автомобиля в области памяти вместе с ним будет уничтожен и объект двигателя. И в этом плане объект автомобиля является главным, а объект двигателя - зависимой.
На диаграммах UML отношение композиции проявляется в обычной стрелке от главной сущности к зависимой, при этом со стороны главной сущности, которая содержит, объект второй сущности, располагается закрашенный ромбик:
Агрегация От композиции следует отличать агрегацию. Она также предполагает отношение
HAS A, но реализуется она иначе:
1 2
3 4
5 6
7 8
9 10 11 public abstract class Engine
{ } public class Car
{
Engine engine; public Car(Engine eng)
{ engine = eng;
}
}
При агрегации реализуется слабая связь, то есть в данном случае объекты Car и Engine будут равноправны. В конструктор Car передается ссылка на уже имеющийся объект
Engine. И, как правило, определяется ссылка не на конкретный класс, а на
абстрактный класс или интерфейс, что увеличивает гибкость программы.
Отношение агрегации на диаграммах UML отображается также, как и отношение композиции, только теперь ромбик будет незакрашенным:
При проектировании отношений между классами надо учитывать некоторые общие рекомендации. В частности, вместо наследования следует предпочитать композицию. При наследовании весь функционал класса-наследника жестко определен на этапе компиляции. И во время выполнения программы мы не можем его динамически переопределить. А класс-наследник не всегда может переопределить код, который определен в родительском классе. Композиция же позволяет динамически определять поведение объекта во время выполнения, и поэтому является более гибкой.
Вместо композиции следует предпочитать агрегацию, как более гибкий способ связи компонентов. В то же время не всегда агрегация уместна. Например, у нас есть класс человека, который содержит объект нервной системы. Понятно, что в реальности, по крайней мере на текущий момент, невозможно вовне определить нервную систему и внедрить ее в человека. То есть в данном случае человек будет главным компонентом, а нервная система - зависимым, подчиненным, и их создание и жизненный цикл будет происходить совместно, поэтому здесь лучше выбрать композицию.