Добавил:
Преподаватель Колледжа информационных технологий Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / Дополнительные возможности ООП

.pdf
Скачиваний:
39
Добавлен:
08.05.2022
Размер:
710.47 Кб
Скачать

Глава 21. Дополнительные возможности ООП в C#

 

Оглавление

 

§21.1 Методы расширения .....................................................................................

1

§21.2 Частичные классы и методы ........................................................................

3

§21.3 Анонимные типы...........................................................................................

5

§21.4 Локальные функции......................................................................................

8

§21.5 Переменные-ссылки и возвращение ссылки ..............................................

9

§21.1 Методы расширения

Методы расширения позволяют добавлять новые методы в уже существующие типы без создания нового производного класса. Эта функциональность бывает особенно полезна, когда нам хочется добавить в некоторый тип новый метод, но сам тип (класс или структуру) мы изменить не можем, поскольку у нас нет доступа к исходному коду. Либо если мы не можем использовать стандартный механизм наследования, например, если классы определенны с модификатором sealed.

Например, нам надо добавить для типа string новый метод:

 

Листинг 21.1 – Метод расширения StringExtension

 

 

 

1

class Program

 

2

{

 

3

static void Main(string[] args)

 

4

{

 

5

string s = "Привет мир";

 

6

char c = 'и';

 

7

int i = s.CharCount(c);

 

8

Console.WriteLine(i);

 

9

Console.Read();

 

10

}

 

11

}

 

12

public static class StringExtension

 

13

{

 

14

public static int CharCount(this string str, char

 

 

c)

 

15

{

 

16

int counter = 0;

 

17

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

18

{

19

if (str[i] == c)

20

counter++;

21

}

22

return counter;

23

}

24

}

Для того, чтобы создать метод расширения, вначале надо создать статический класс, который и будет содержать этот метод. В данном случае это класс StringExtension. Затем объявляем статический метод. Суть нашего метода расширения - подсчет количества определенных символов в строке.

Собственно, метод расширения - это обычный статический метод,

который в качестве первого параметра всегда принимает такую конструкцию:

this имя_типа название_параметра

, то есть в нашем случае this string str. Так как наш метод будет относиться к типу string, то мы и используем данный тип.

Затем у всех строк мы можем вызвать данный метод:

int i = s.CharCount(c);

Причем нам уже не надо указывать первый параметр. Значения для остальных параметров передаются в обычном порядке.

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

Также следует учитывать, что методы расширения действуют на уровне пространства имен. То есть, если добавить в проект другое пространство имен, то метод не будет применяться к строкам, и в этом случае надо будет подключить пространство имен метода через директиву using.

§21.2 Частичные классы и методы

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

Например, определим в проекте два файла с кодом. Не столь важно, как эти файлы будут называться. Например, PersonBase.cs и PersonAdditional.cs.

В одном из этих файлов (без разницы в каком именно) определим следующий класс:

Листинг 21.2

1public partial class Person

2{

3public void Move()

4{

5 Console.WriteLine("I am moving");

6}

7}

Ав другом файле определим следующий класс:

Листинг 21.3

1

public partial class Person

2

{

3

public void Eat()

4

{

5

Console.WriteLine("I am eating");

6

}

7

}

Таким образом, два файла в проекте содержит определение одного и того же класса Person, которые содержат два разных метода. И оба определенных здесь класса являются частичными. Для этого они определяются с ключевым словом partial.

 

Рисунок 21.1

 

Затем мы можем использовать все методы класса Person:

 

Листинг 21.4

 

 

 

1

class Program

 

2

{

 

3

static void Main(string[] args)

 

4

{

 

5

Person tom = new Person();

 

6

tom.Move();

 

7

tom.Eat();

 

8

Console.ReadKey();

 

9

}

 

10

}

 

 

Частичные классы могут содержать частичные методы. Такие методы

также определяются с ключевым словом partial. Причем определение частичного метода без тела метода находится в одном частичном классе, а

реализация этого же метода - в другом частичном классе.

 

 

Например, изменим выше определенные классы Person. Первый

класс:

 

 

Листинг 21.5

 

 

 

 

 

1

public partial class Person

 

 

2

{

 

 

3

partial void DoSomethingElse();

 

 

4

public void DoSomething()

 

 

5

{

 

 

6

Console.WriteLine("Start");

 

 

7

DoSomethingElse();

 

 

8

Console.WriteLine("Finish");

 

 

9

}

 

 

10

}

 

Второй класс:

Листинг 21.5

1public partial class Person

2{

3partial void DoSomethingElse()

4{

5 Console.WriteLine("I am reading a book");

6}

7}

Впервом классе определен метод DoSomethingElse, который

вызывается в методе DoSomething. Причем на момент определения первого класса неизвестно, что представляет собой метод DoSomethingElse. Тем не менее мы знаем список его параметров и может вызвать в первом классе.

Второй класс уже непосредственно определяет тело метода

DoSomethingElse.

При этом частичные методы не могут иметь модификаторов доступа -

по умолчанию они все считаются приватными. Также частичные методы не могут иметь таких модификаторов как virtual, abstract, override, new, sealed. Хотя допустимы статические частичные методы.

Кроме того, частичные методы не могут возвращать значения, то есть они всегда имеют тип void. И также они не могут иметь out-параметров.

Поскольку частичные методы всегда приватные, то мы не сможем иx

вызвать напрямую в программе вне классов, где они определены. Поэтому обычно они вызываются через другие доступные методы как в случае выше через метод DoSomething:

Листинг 21.6

1Person tom = new Person()

2tom.DoSomething()

§21.3 Анонимные типы

Анонимные типы позволяют создать объект с некоторым набором свойств без определения класса. Анонимный тип определяется с помощью ключевого слова var и инициализатора объектов:

var user = new { Name = "Tom", Age = 34 };

В данном случае user - это объект анонимного типа, у которого определены два свойства Name и Age. И мы также можем использовать его свойства, как и у обычных объектов классов. Однако тут есть ограничение -

свойства анонимных типов доступны только для чтения.

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

представлять ссылочный тип.

Если в программе используются несколько объектов анонимных типов с одинаковым набором свойств, то для них компилятор создаст одно определение анонимного типа.

Листинг 21.7

1var user = new { Name = "Tom", Age = 34 }

2var student = new { Name = "Alice", Age = 21 }

3

var manager = new { Name = "Bob", Age = 26, Company = "Microsoft" }

4

Console.WriteLine(user.GetType().Name) // Возвращает тип <>f__AnonymousType0'2

5

Console.WriteLine(student.GetType().Name) // Возвращает тип <>f__AnonymousType0'2

6

Console.WriteLine(manager.GetType().Name) // Возвращает тип <>f__AnonymousType1'3

Здесь user и student будут иметь одно и то же определение анонимного типа. Однако подобные объекты нельзя преобразовать к какому-

нибудь другому типу, например, классу, даже если он имеет подобный набор свойств.

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

Кроме использованной выше формы инициализации, когда мы присваиваем свойствам некоторые значения, также можно использовать

инициализаторы с проекцией, когда мы можем передать в инициализатор

некоторые идентификаторы, имена которых будут использоваться как

названия свойств:

Листинг 21.8

1

class User

2

{

3

public string Name { get; set; }

4

}

5

class Program

6

{

7

static void Main(string[] args)

8

{

9

User tom = new User { Name = "Tom" };

10

int age = 34;

11

var student = new { tom.Name, age}; //

инициализатор с проекцией

12

Console.WriteLine(student.Name);

13

Console.WriteLine(student.age);

14

Console.Read();

15

}

16}

Вданном случае определение анонимного объекта фактически будет

идентично следующему:

var student = new { Name = tom.Name, age = age};

Названия свойств и переменных (Name и age) будут использоваться в качестве названий свойств объекта.

Зачем нужны анонимные типы? Иногда возникает задача использовать один тип в одном узком контексте или даже один раз. Создание класса для подобного типа может быть избыточным. Если нам захочется добавить свойство, то мы сразу же на месте анонимного объекта это можем сделать. В

случае с классом придется изменять еще и класс, который может больше нигде не использоваться. Типичная ситуация - получение результата выборки из базы данных: объекты используются только для получения выборки, часто больше нигде не используются, и классы для них создавать было бы излишне. А вот анонимный объект прекрасно подходит для временного хранения выборки.

§21.4 Локальные функции

Локальные функции представляют функции, определенные внутри

других методов. Определим и используем локальную функцию:

 

 

Листинг 21.9 – Определение локальной функции

 

 

 

 

 

1

class Program

 

 

 

 

 

 

 

 

 

2

{

 

 

3

static void Main(string[] args)

 

 

4

{

 

 

5

var result = GetResult(new int[] { -3, -2, -

 

 

1, 0, 1, 2, 3 });

 

 

 

 

 

6

Console.WriteLine(result); // 6

 

 

7

Console.Read();

 

 

8

}

 

 

9

static int GetResult(int[] numbers)

 

 

10

{

 

 

11

int limit = 0;

 

 

12

// локальная функция

 

 

13

bool IsMoreThan(int number)

 

 

14

{

 

 

15

return number > limit;

 

 

16

}

 

 

17

int result = 0;

 

 

18

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

 

 

19

{

 

 

20

if (IsMoreThan(numbers[i]))

 

 

21

{

 

 

22

result += numbers[i];

 

 

23

}

 

 

24

}

 

 

25

return result;

 

 

26

}

 

 

27

}

 

 

 

Здесь в методе GetResult определена локальная функция

IsMoreThan(), которая может быть вызвана только внутри этого метода.

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

При использовании локальных функций следует помнить, что они не могут иметь модификаторов доступа (public, private, protected). Нельзя определить в одном методе несколько локальных функций с одним и тем же именем, даже если у них будет разный список параметров. Кроме того, не имеет значения, определены локальные функции до своего вызова или после.

§21.5 Переменные-ссылки и возвращение ссылки

Для определения локальной переменной-ссылки (ref local) перед ее типом ставится ключевое слово ref:

int x = 5;

ref int xRef = ref x;

Здесь переменная xRef указывает не просто на значение переменной x,

а на область в памяти, где располагается эта переменная. Для этого перед x

также указывается ref.

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

Получив ссылку, мы можем манипулировать значением по этой ссылке. Например:

Листинг 21.10

1 int x = 5

2 ref int xRef = ref x

3 Console.WriteLine(x) // 5

4 xRef = 125

5 Console.WriteLine(x) // 125

6 x = 625

7 Console.WriteLine(xRef) // 625

Для возвращения из функции ссылки в сигнатуре функции перед

возвращаемым типом, а также после оператора return следует указать

ключевое слово ref:

Листинг 21.11

1static void Main(string[] args)

2{

3

int[] numbers = { 1, 2, 3, 4, 5, 6, 7 };

4

ref int numberRef = ref Find(4, numbers); // ищем

число 4 в массиве

 

5

numberRef = 9; // заменяем 4 на 9

6

Console.WriteLine(numbers[3]); // 9

7

Console.Read();

8

}

9

static ref int Find(int number, int[] numbers)

10

{

11

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

12

{

13

if (numbers[i] == number)

14

{

15

return ref numbers[i]; // возвращаем

ссылку на адрес, а не само значение

16

}

17

}

18

throw new IndexOutOfRangeException("число не

найдено");

19}

Вметоде Find ищем число в массиве, но вместо самого значения числа

возвращаем ссылку на него в памяти. Для этого в сигнатуре метода в качестве типа результата функции указывается не просто int, а ref int.

Кроме того, в самом методе после слова return также ставится ref

(строка 15). Тем самым мы получаем не просто значение, а ссылку на объект

впамяти.

Вметоде Main для определения переменной, которая будет содержать ссылку, используется ключевое слово ref. При вызове метода также указывается слово ref (строка 4).

Витоге переменная numberRef будет содержать ссылку на объект int,

и через данную переменную в последствии мы можем изменить объект по этой ссылке.

При определении метода, который возвращает ссылку, следует учитывать, что такой метод естественно не может иметь тип void. Кроме того, такой метод не может возвращать:

Значение null;