Введение в регулярные выражения 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Введение в регулярные выражения



 

Язык регулярных выражений предназначен специально для обработки строк. Он включает два средства:

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

 

С помощью регулярных выражений можно выполнять достаточно сложные и высокоуровневые действия над строками:

  • Идентифицировать (и возможно, помечать к удалению) все повторяющиеся слова в строке.
  • Сделать заглавными первые буквы всех слов.
  • Преобразовать первые буквы всех слов длиннее трех символов в заглавные.
  • Обеспечить правильную капитализацию предложений (изменение букв с заглавные на сточные и обратно).
  • Выделить различные элементы в URI (например, имея ссылку http://www.microsoft.com, выделить протокол, имя компьютера, имя файла и т.д.).

 

Разумеется, все эти задачи можно решить на С# с использованием различных методов System.String и System.Text.StringBuilder. Однако в некоторых случаях это потребует написания большого объёма кода С#. Если используются регулярные выражения, то весь этот код сокращается буквально до нескольких строк. По сути, программист создаёт экземпляр объекта System.Text.RegularExpressions.RegEx (или, что ещё проще, вызывается статический метод RegEx), передаете ему строку для обработки, а также само регулярное выражение (строку, включающую инструкции на языке регулярных выражений) — и всё готово.

 

В следующей таблице показана часть информации о перечислениях RegexOptions:

 

Член Описание
Culturelnvariant Предписывает игнорировать национальные установки строки
ExplicitCapture Модифицирует способ поиска соответствия, обеспечивая только буквальное соответствие
IgnoreCase Игнорирует регистр символов во входной строке
IgnorePatternWhitespace Удаляет из строки не защищенные управляющими символами пробелы и разрешает комментарии, начинающиеся со знака фунта или хеша
Multiline Изменяет значение символов ^ и $ так, что они применяются к началу и концу каждой строки, а не только к началу и концу всего входного текста
RightToLeft Предписывает читать входную строку справа налево вместо направления по умолчанию — слева направо (что удобно для некоторых азиатских и других языков, которые читаются в таком направлении)
Singleline Специфицирует однострочный режим, в котором точка (.) символизирует соответствие любому символу

 

Главным преимуществом регулярных выражений является использование метасимволов — специальные символы, задающие команды, а также управляющие последовательности, которые работают подобно управляющим последовательностям С#. Это символы, предваренные знаком обратного слеша (\) и имеющие специальное назначение. Некоторые из данных метасимволов перечислены в таблице:

 

Символ Значение Пример Соответствует
^ Начало входного текста ^B B, но только как первый символ текста
$ Конец входного текста X$ X, но только как последний символ текста
. Любой одиночный символ кроме символа перевода строки (\n) i.text iqtext, iftext,...
* Предыдущий символ может повторяться 0 или более раз on*e oe, one, onne, onnne,...
+ Предыдущий символ может повторяться 1 или более раз on+e one, onne, onnne,... (но не oe)
? Предыдущий символ может повторяться 0 или 1 раз on?e oe, one
\s Любой пробельный символ \sa [пробел]а, \ta, \na (\t и \n имеют тот же смысл, что и в С#)
\S Любой символ, не являющийся пробелом \SF aF, rF, cF, но не \tf
\b Граница слова ция\b Любое слово, заканчивающееся на «ция»
\B Любая позиция, кроме границы слова \BX\B Любой символ х в середине слова

 

Давайте рассмотрим пример использования регулярных выражений, где будем искать в исходном тексте слово «сериализация» и его однокоренные слова, при этом выделяя в консоли их другим цветом:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Text.RegularExpressions;

 

namespace LC_Console

{

class Program

{

static void Main()

{

ConsoleColor c = new ConsoleColor();

c = Console.ForegroundColor;

string myText = @"Сериализация представляет собой процесс сохранения объекта на диске.

В другой части приложения или даже в совершенно отдельном приложении может производиться

десериализация объекта, возвращающая его в состояние, в котором он пребывал до сериализации.";

const string myReg = "со";

MatchCollection myMatch = Regex.Matches(myText,myReg);

Console.WriteLine("Все вхождения строки \"{0}\" в исходной строке: ",myReg);

foreach (Match i in myMatch)

Console.Write("\t"+i.Index);

// Услажним шаблон регулярного выражения

// введя в него специальные метасимволы

const string myReg1 = @"\b[с,д]\S*ериализац\S*";

MatchCollection match1 = Regex.Matches(myText,myReg1,RegexOptions.IgnoreCase);

findMyText(myText,match1);

Console.BackgroundColor = ConsoleColor.Black;

Console.ForegroundColor = c;

Console.WriteLine("\n\nДля продолжения нажмите любую клавишу...");;

Console.ReadKey();

}

 

static void findMyText(string text, MatchCollection myMatch)

{

Console.WriteLine("\n\nИсходная строка:\n\n{0}\n\nВидоизменённая строка:\n", text);

// Реализуем выделение ключевых слов в консоли другим цветом

for (int i = 0; i < text.Length; i++)

{

 

foreach (Match m in myMatch)

{

 

if ((i >= m.Index) && (i < m.Index + m.Length))

{

Console.BackgroundColor = ConsoleColor.Green;

Console.ForegroundColor = ConsoleColor.Black;

break;

}

else

{

Console.BackgroundColor = ConsoleColor.Black;

Console.ForegroundColor = ConsoleColor.White;

}

}

Console.Write(text[i]);

}

}

}

}

 

Рис. 2. 1. Результат работы кода выше

 

Для проверки гибкости работы регулярных выражений, подставьте в исходный текст ещё несколько слов «сериализация», можно увидеть, что он будет автоматически выделен зелёным цветом в окне консоли.

Понятие «Перечисление»

Понятие «Перечисление»

 

Перечисление (enumeration) — это определяемый пользователем целочисленный тип в C#. Когда объявляется перечисление, то специфицируется набор допустимых значений, которые могут принимать экземпляры перечислений. Но мало того — этим значениям ещё должны быть присвоены имена, понятные для пользователей. Если где-то в коде попытаться присвоить экземпляру перечисления значение, не входящее в список допустимых, компилятор выдаст ошибку.

 

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

  • Как упоминалось, перечисления облегчают сопровождение кода, гарантируя, что переменным будут присваиваться только легитимные, ожидаемые значения.
  • Перечисления делают код яснее, позволяя обращаться к целым значениям, называя их осмысленными именами вместо малопонятных "магических" чисел.
  • Перечисления облегчают ввод исходного кода. Когда вы собираетесь присвоить значение экземпляру перечислимого типа, то среда разработки Visual Studio 2010 с помощью средства IntelliSense отображает всплывающий список с допустимыми значениями, что позволяет сэкономить несколько нажатий клавиш и напомнить о возможном выборе значений.

 

Перечислимый тип данных объявляется с помощью ключевого слова enum. Ниже приведена общая форма объявления перечисления:

 

enum <имя перечисления> { <списокперечисления> };

 

Где имя перечисления — это имя типа перечисления, а список перечисления — список идентификаторов, разделяемый запятыми.

Следует особо подчеркнуть, что каждая символически обозначаемая константа в перечислении имеет целое значение. Тем не менее неявные преобразования перечислимого типа во встроенные целочисленные типы и обратно в С# не определены, а значит, в подобных случаях требуется явное приведение типов. Кроме того, приведение типов требуется при преобразовании двух перечислимых типов. Но поскольку перечисления обозначают целые значения, то их можно, например, использовать для управления оператором выбора switch или же оператором цикла for.

Для каждой последующей символически обозначаемой константы в перечислении задается целое значение, которое на единицу больше, чем у предыдущей константы. По умолчанию значение первой символически обозначаемой константы в перечислении равно нулю.

 

Рассмотрим пример использования перечислений:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

// Создаём перечисление (используем русскием имена для списка перечисления)

enum UI: long { Имя, Семья, КороткоеИмя = 5, Возраст, Пол }

enum Address: long { Country, Town, Street, Building, Appartement }

 

class Program

{

static void Main()

{

UI user1;

Address address1;

for (user1 = UI.Имя; user1 <= UI.Пол; user1++)

Console.WriteLine("Элемент: \"{0}\", значение {1}", user1, (int)user1);

Console.WriteLine();

for (address1 = Address.Country; address1 <= Address.Appartement; address1++)

Console.WriteLine("Элемент: \"{0}\", значение {1}", address1, (int)address1);

Console.WriteLine("Для продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

 

Рис. 1. 1. Результат работы кода выше

 

Значение одной или нескольких символически обозначаемых констант в перечислении можно задать с помощью инициализатора. Для этого достаточно указать после символического обозначения отдельной константы знак равенства и целое значение. Каждой последующей константе присваивается значение, которое на единицу больше значения предыдущей инициализированной константы. В приведённом выше примере инициализируется константа «КороткоеИмя».

По умолчанию в качестве базового для перечислений выбирается тип int, тем не менее, перечисление может быть создано любого целочисленного типа, за исключением char. Для того чтобы указать другой тип, кроме int, достаточно поместить этот тип после имени перечисления, отделив его двоеточием.

Изменять базовый тип перечислений удобно в случае создания таких приложений .NET, которые будут развертываться на устройствах с небольшим объёмом памяти (таких как поддерживающие.NET-платформу для приложений сегмента мобильных устройств на подобии коммуникатором, планшетов и прочее), чтобы экономить память везде, где только возможно. Естественно, если для перечисления в качестве базового типа указан byte, каждое значение в этом перечислении ни в коем случае не должно выходить за рамки диапазона его допустимых значений.

 

Перечисления очень широко применяются во всех библиотеках базовых классов.NET. Например, в ADO.NET множество перечислений используется для обозначения состояния соединения с базой данных (например, открыто оно или закрыто) и состояния строки в DataTable (например, является она измененной, новой или отсоединенной). Поэтому в случае применения любых перечислений следует всегда помнить о наличии возможности взаимодействовать с парами "имя/значение" в них с помощью членов System.Enum.

Понятие «Интерфейс»

Понятие «Интерфейс»

 

Понятие «Интерфейс»

 

Интерфейсы описывают группу связанных функциональных возможностей, которые могут принадлежать к любому классу или структуре. Интерфейсы определяются с помощью ключевого слова interface, как показано в следующем примере:

 

interface IEquatable<T>

{

bool Equals(T obj);

}

 

IEquatable<T> определяет обобщённый метод сравнения, тип значения или класс которого используется для определения равенства экземпляров.

 

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

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

Классы и структуры реализуют интерфейсы таким же образом, как классы наследуются от базового класса или структуры, но есть два исключения:

  • Класс или структура может реализовать несколько интерфейсов.
  • Если класс или структура реализует интерфейс, она получает только имена и сигнатуры метода, поскольку в интерфейсе не содержится никаких реализаций, как показано в следующем примере:

 

public class Car: IEquatable<Car>

{

public string Make { get; set; }

public string Model { get; set; }

public string Year { get; set; }

// Реализация IEquatable<T> интерфейса из кода выше

public bool Equals(Car car)

{

if (this.Make == car.Make &&

this.Model == car.Model &&

this.Year == car.Year)

{

return true;

}

else

return false;

}

}

 

Интерфейс IEquatable<T> объявляет пользователю объекта, что объект может определять, равен ли он другому объекту такого же типа, причём пользователь интерфейса не должен знать, как именно это реализовано.

Для реализации члена интерфейса соответствующий член класса должен быть открытым и не статическим, он должен обладать таким же именем и сигнатурой, как член интерфейса. Свойства и индексаторы класса могут определить дополнительные методы доступа для свойства или индексатора, определённого в интерфейсе. Например, интерфейс может объявлять свойство, имеющее метод доступа get. Класс, реализующий интерфейс может объявлять это же свойство с get и set методами доступа. Если же свойство или индексатор использует явную реализацию, методы доступа должны совпадать.

Интерфейсы и члены интерфейсов являются абстрактными. Интерфейсы не имеют реализации по умолчанию.

Интерфейсы могут наследовать другие интерфейсы.

Класс может наследовать интерфейс несколько раз посредством наследуемых базовых классов или посредством интерфейсов, наследуемых другими интерфейсами. Однако класс может реализовать интерфейс только один раз, и только если интерфейс объявляется в рамках определения класса, как в class ClassName: InterfaceName. Если интерфейс унаследован вследствие наследования базового класса, реализующего интерфейс, его реализация предоставляется базовым классом. Базовый класс также может реализовать члены интерфейса с помощью виртуальных членов. В этом случае производный класс может изменить поведение интерфейса путем переопределения виртуальных членов.

 

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

 

public interface IUIControl

{

void Paint();

}

 

public class Button: IUIControl

{

public void Paint()

{

// Рисуем кнопку

}

}

 

public class ListBox: IUIControl

{

public void Paint()

{

// Рисуем выпадающий список

}

}

 

 

В данном случае создаётся интерфейс IUIControl, в котором определяем, но не реализуем метод Paint. В свою очередь классы Button и ListBox реализуют этот интерфейс и переопределяют метод Paint. Концептуально интерфейсы представляют собой связки между двумя в корне отличными частями кода. Иначе говоря, при наличии интерфейса и класса, определённого как реализующий данный интерфейс, клиентам класса даётся гарантия, что у класса реализованы все методы, определённые в интерфейсе. По умолчанию все члены интерфейса являются общедоступными, чтобы их мог реализовать какой-либо класс.

 

Интерфейс имеет следующие свойства:

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

 

Явная реализация интерфейса

 

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

 

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

 

interface IControl

{

void Paint();

}

 

interface ISurface

{

void Paint();

}

 

class SampleClass: IControl, ISurface

{

// Оба интерфейса ISurface.Paint и IControl.Paint вызывают этот метод

public void Paint()

{

Console.WriteLine("Paint");

}

}

 

Однако, если члены двух интерфейсов не выполняют одинаковую функцию, это может привести к неверной реализации одного или обоих интерфейсов. Возможна явная реализация члена интерфейса — путём создания члена класса, который вызывается только через интерфейс и имеет отношение только к этому интерфейсу. Это достигается путем включения в имя члена класса имени интерфейса с точкой. Пример:

 

public class SampleClass: IControl, ISurface

{

void IControl.Paint()

{

System.Console.WriteLine("IControl.Paint");

}

 

void ISurface.Paint()

{

System.Console.WriteLine("ISurface.Paint");

}

}

 

Член класса IControl.Paint доступен только через интерфейс IControl, а член ISurface.Paint — только через интерфейс ISurface. Каждая реализация метода является независимой и недоступна в классе напрямую. Пример (вызов из метода Main программы):

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

interface IControl

{

void Paint();

}

 

interface ISurface

{

void Paint();

}

 

public class SampleClass: IControl, ISurface

{

void IControl.Paint()

{

System.Console.WriteLine("IControl.Paint");

}

 

void ISurface.Paint()

{

System.Console.WriteLine("ISurface.Paint");

}

}

 

 

public class Program

{

static void Main()

{

SampleClass obj = new SampleClass();

//obj.Paint(); // Ошибка компилятора

IControl c = (IControl)obj;

c.Paint(); // Вызывает IControl.Paint из SampleClass

ISurface s = (ISurface)obj;

s.Paint(); // Вызывает ISurface.Paint из SampleClass.

 

Console.WriteLine("Для продолжение нажмите любую клавишу... ");

Console.ReadKey();

}

}

}

/* Выведет:

* IControl.Paint

* ISurface.Paint

* Для продолжение нажмите любую клавишу...

*/

 

Явная реализация также используется для разрешения случаев, когда каждый из двух интерфейсов объявляет разные члены с одинаковым именем, например свойство и метод:

 

interface ILeft

{

int P { get; }

}

 

interface IRight

{

int P();

}

 

Для реализации обоих интерфейсов классу необходимо использовать явную реализацию либо для свойства P, либо для метода P, либо для обоих членов, чтобы избежать ошибки компилятора. Пример:

 

class Middle: ILeft, IRight

{

public int P() { return 0; }

int ILeft.P { get { return 0; } }

}

 

В этом примере (ниже) выполняется объявление интерфейса, IDimensions, и класса, Box, который явно реализует члены интерфейса getLength и getWidth. Доступ к этим членам осуществляется через экземпляр интерфейса dimensions:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

interface IDimensions

{

float getLength();

float getWidth();

}

 

class Box: IDimensions

{

float lengthInches;

float widthInches;

 

Box(float length, float width)

{

lengthInches = length;

widthInches = width;

}

// Явная реализация члена интерфейса

float IDimensions.getLength()

{

return lengthInches;

}

// Явная реализация члена интерфейса

float IDimensions.getWidth()

{

return widthInches;

}

 

static void Main()

{

// Объявляем экземпляр класса Box и передаём туда размеры

Box box1 = new Box(30.0f, 20.0f);

// Объявляем интерфейс получающий размеры

IDimensions dimensions = (IDimensions)box1;

// Две строки кода закомментированы так как вызовут ошибки компилятора

//Console.WriteLine("Length: {0}", box1.getLength());

//Console.WriteLine("Width: {0}", box1.getWidth());

// Выводит размеры из экземпляра класса Box c помощью методов из экземпляра интерфейса

Console.WriteLine("Длина: {0}", dimensions.getLength());

Console.WriteLine("Ширина: {0}", dimensions.getWidth());

}

}

}

/* Выведет:

* Длниа: 30

* Ширина: 20

* Для продолжение нажмите любую клавишу...

*/

 

Обратим внимание, что следующие строки в методе Main убраны в комментарий, так как иначе они вызвали бы ошибки компилятора. Член интерфейса, реализованный явным образом, недоступен из экземпляра класса:

 

//Console.WriteLine("Length: {0}", box1.getLength());

//Console.WriteLine("Width: {0}", box1.getWidth());

 

Обратите также внимание, что следующие строки в методе Main успешно печатают размеры поля, поскольку эти методы вызываются из экземпляра интерфейса:

 

Console.WriteLine("Длина: {0}", dimensions.getLength());

Console.WriteLine("Ширина: {0}", dimensions.getWidth());

 



Поделиться:


Последнее изменение этой страницы: 2016-08-15; просмотров: 429; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.224.64.226 (0.229 с.)