Классы в С#. Класс Класс это логическая структура, позволяющая создавать свои собственные пользовательские типы. Класс определяет данные и поведение типа.

Презентация:



Advertisements
Похожие презентации
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Advertisements

Класс может содержать объявления следующих членов: Конструкторы Деструкторы Константы неизменные значения, известные во время компиляции и неизменяемые.
Перегрузка операторов x = a + b результат 1-й операнд2-й операнд оператор По количеству операндов операторы делятся на: унарные (один операнд) бинарные.
Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
Наследование Полиморфизм ВЫЗОВ КОНСТРУКТОРОВ И ДЕСТРУКТОРОВ ПРИ НАСЛЕДОВАНИИ.
Учебный курс Объектно-ориентированный анализ и программирование Лекция 7 Методы как средство реализации операций Лекции читает кандидат технических наук.
Основы ООП и C# Работа с объектами и классами. Классы Класс специальный тип данных для описания объектов. Он определяет данные и поведение типа. Определение.
Полиморфизм Полиморфизм (polymorphism) - последний из трех "китов", на которых держится объектно-ориентированное программирование Слово это можно перевести.
С# и ООП Формальное определение класса с C# Класс в C# - это пользовательский тип данных (user defined type), который состоит из данных (часто называемых.
©Павловская Т.А. (СПбГУ ИТМО) Курс «С#. Программирование на языке высокого уровня» Павловская Т.А.
Кафедра ОСУ, Java 2004 Слайд 1 Наследование Наследование позволяет использовать существующий класс для определения новых классов, т.е. способствует.
Наследование. Наследование – это свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся.
НГТУ, каф. ВТ Наследование в С++ Макаревич Л. Г.НГТУ, каф. ВТ Наследование в С++ Макаревич Л. Г.
Классы в С#. Перечисления С# Перечисление задает конечное множество возможных значений, которые могут получать объекты класса перечисление. [атрибуты][модификаторы]
1 Классы в Java Ключевое слово class означает: Я говорю тебе, как выглядит новый тип объекта. Класс является базовым элементом объектно-ориентированного.
Обработка исключительных ситуаций Исключительная ситуация (исключение) – это ошибка, возникающая во время выполнения программы. Например, ошибка работы.
Объектно-ориентированное программирование С++. Лекция 6 Карпов В.Э.
В С# предусмотрены средства для создания пользовательских классов-контейнеров, к внутренним элементам которых можно обращаться при помощи того же оператора.
Транксрипт:

Классы в С#

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

Объявление классов Классы объявляются с помощью ключевого слова class. public class Customer { //Fields, properties, methods and events go here... } Ключевому слову class предшествует уровень доступа. Поскольку в данном случае используется public, любой может создавать объекты из этого класса. Имя класса указывается после ключевого слова class. Оставшаяся часть определения является телом класса, в котором задаются данные и поведение. члены класса Поля, свойства, методы и события в классе обозначаются термином члены класса.

Создание объектов Классобъект Класс и объект это разные вещи. Класс определяет тип объекта, но не сам объект. Объект Объект это конкретная сущность, основанная на классе и иногда называемая экземпляром класса. Объекты Объекты можно создавать с помощью ключевого слова new, за которым следует имя класса, на котором будет основан объект: Customer object1 = new Customer();

При создании экземпляра класса ссылка на этот объект передается программисту. ссылку на объект В предыдущем примере object1 является ссылкой на объект, основанный на Customer. Эта ссылка указывает на новый объект, но не содержит данные этого объекта. Фактически, можно создать ссылку на объект без создания самого объекта: Customer object2; Создание таких ссылок, которые не указывают на объект, не рекомендуется, так как попытка доступа к объекту по такой ссылке приведет к сбою во время выполнения. Однако такую ссылку можно сделать указывающей на объект, создав новый объект или назначив ее существующему объекту: Customer object3 = new Customer(); Customer object4 = object3 ; Customer object1 = new Customer();

Инкапсуляция Об инкапсуляции иногда говорят как о первом базовом элементе или принципе объектно-ориентированного программирования. Согласно принципу инкапсуляции класс или структура может задать уровень доступности каждого из членов по отношению к коду вне класса или структуры. Методы и переменные, которые не предназначены для использования вне класса или сборки, могут быть скрыты, чтобы ограничить потенциальную угрозу возникновения ошибок кода или вредоносное использование

Модификаторы доступа Все типы и члены типов имеют уровень доступности, который определяет возможность их использования из другого кода в сборке разработчика или других сборках. Можно использовать следующие модификаторы доступа для указания доступности типа или члена при объявлении этого типа или члена. public private protected internal protected internal

public Доступ к типу или члену возможен из любого другого кода в той же сборке или другой сборки, ссылающейся на него. private Доступ к типу или члену можно получить только из кода в том же классе или структуре. protected Доступ к типу или элементу можно получить только из кода в том же классе или структуре, либо в производном классе. internal Доступ к типу или члену возможен из любого кода в той же сборке, но не из другой сборки. protected internal Доступ к типу или элементу может осуществляться любым кодом в сборке, в которой он объявлен, или из наследованного класса другой сборки. Доступ из другой сборки должен осуществляться в пределах объявления класса, производного от класса, в котором объявлен защищенный внутренний элемент, и должен происходить через экземпляр типа производного класса.

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

Однако, поскольку базовый класс может сам наследовать от другого класса, класс может косвенно наследовать несколько базовых классов. Кроме того, класс может напрямую реализовать несколько интерфейсов. Если ClassC является производным от ClassB, и ClassB является производным от ClassA, ClassC наследует члены, объявленные в ClassB и ClassA. Наследование выполняется с помощью образования производных классов, то есть класс объявляется с помощью базового класса, от которого он наследует данные и поведение. Базовый класс задается добавлением после имени производного класса двоеточия и имени базового класса: public class Manager : Employee { // Employee fields, properties, methods and events are inherited // New Manager fields, properties, methods and events go here... } Структуры не поддерживают наследование.

Пример реализации принципов наследования: using System; namespace Inheritance_1 { public class A { public int val1_A; public void fun1_A (String str) { Console.WriteLine("A's fun1_A:" + str); } } public class B:A { public int val1_B; public void fun1_B (String str) { Console.WriteLine("B's fun1_B:" + str); } } class Class1 { static void Main(string[ ] args) { B b0 = new B( ); // От имени объекта b0 вызвана собственная функция fun1_B. b0.fun1_B("from B"); // От имени объекта b0 вызвана унаследованная от класса A функция fun1_A. b0.fun1_A("from B"); }

Наследование и проблемы доступа Производный класс наследует от базового класса ВСЕ, что он имеет. Другое дело, что воспользоваться в производном классе можно не всем. Добавляем в базовый класс private-члены: public class A { public int val1_A = 0; public void fun1_A (String str) { Console.WriteLine("A's fun1_A:" + str); this.fun2_A("private function from A:"); } private int val2_A = 0; private void fun2_A (String str) { Console.WriteLine(str + "A's fun2_A:" + val2_A.ToString()); } } Теперь объект класса B в принципе НЕ может получить доступ к private данным членам и функциям членам класса A. Косвенное влияние на такие данные-члены и функции члены – лишь через public-функции класса A.

Используем еще один спецификатор доступа – protected. Этот спецификатор обеспечивает открытый доступ к членам базового класса, но только для производного класса! public class A { ::::::::: protected int val3_A = 0; } public class B:A { ::::::::: public void fun1_B (String str) { ::::::::: this.val3_A = 125; } // Нормально } static void Main(string[ ] args) { ::::::::: // b0.val3_A = 125; // Это член класса закрыт для внешнего использования! } Защищенные члены базового класса доступны для ВСЕХ прямых и косвенных наследников данного класса.

Несколько важных замечаний относительно использования спецификаторов доступа: в C# структуры НЕ поддерживают наследования. Поэтому спецификатор доступа protected в объявлении данных членов и функций членов структур НЕ ПРИМЕНЯЕТСЯ; спецификаторы доступа действуют и в рамках пространства имен (поэтому и классы в нашем пространстве имен были объявлены со спецификаторами доступа public). Но в пространстве имен явным образом можно использовать лишь один спецификатор – спецификатор public, либо не использовать никаких спецификаторов.

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

Если в базовом классе будет создан новый член, имя которого совпадает с именем члена в производном классе, в классе должно быть явно указано, будет ли метод переопределять наследуемый метод, или это новый метод, который будет скрывать наследуемый метод с тем же именем. В C# производный класс может включать методы с теми же именами, что и у методов базового класса. Метод базового класса может быть определен как виртуальный. Если перед методом в производном классе не указано ключевое слово new или override, компилятор выдаст предупреждение, и обработка метода будет производиться как в случае наличия ключевого слова new. Если перед методом в производном классе указано ключевое слово new, то этот метод определен как независимый от метода в базовом классе. Если перед методом в производном классе указано ключевое слово override, то объекты производного класса будут вызывать этот метод вместо метода базового класса. Базовый метод можно вызвать из производного класса с помощью ключевого слова base. Ключевые слова override, virtual и new могут также применяться к свойствам, индексам и событиям.

По умолчанию методы в языке C# не являются виртуальными. Если метод объявлен как виртуальный, то любой класс, наследующий этот метод, может реализовать собственную версию. Чтобы сделать метод виртуальным, в объявлении метода базового класса используется модификатор virtual. Производный класс может переопределить базовый виртуальный метод с помощью ключевого слова override или скрыть виртуальный метод в базовом классе с помощью ключевого слова new. Если ключевые слова override и new не указаны, компилятор выдаст предупреждение, и метод в производном классе будет скрывать метод в базовом классе. Чтобы продемонстрировать это на практике, предположим, что компания А создала класс с именем GraphicsClass, используемый вашей программой. class GraphicsClass { public virtual void DrawLine() { } public virtual void DrawPoint() { } }

Вы применили его для создания собственного производного класса, добавив новый метод: class YourDerivedGraphicsClass : GraphicsClass { public void DrawRectangle() { } } Ваше приложение работало без проблем до тех пор, пока компания А не выпустила новую версию класса GraphicsClass со следующим кодом: class GraphicsClass { public virtual void DrawLine() { } public virtual void DrawPoint() { } public virtual void DrawRectangle() { } } После перекомпиляции приложения с помощью новой версии класса GraphicsClass компилятор выдаст предупреждение CS0108. В этом предупреждении вам будет предложено принять решение относительно работы метода DrawRectangle в вашем приложении.

Чтобы избежать путаницы между двумя методами, вы можете переименовать свой метод. Это отнимает много времени и может привести к возникновению ошибок, поэтому не всегда удобно. Если ваш метод должен переопределить новый метод базового класса, используйте ключевое слово override: Объекты, являющиеся производными от класса YourDerivedGraphicsClass, могут продолжать использовать версию метода DrawRectangle базового класса, используя ключевое слово "base": class YourDerivedGraphicsClass : GraphicsClass { public override void DrawRectangle() { } } base.DrawRectangle();

Чтобы избежать появления предупреждения, можно также использовать ключевое слово new в определении производного класса. class YourDerivedGraphicsClass : GraphicsClass { public new void DrawRectangle() { } }

Полиморфизм О полиморфизме часто говорят как о третьем базовом элементе объектно- ориентированного программирования, после инкапсуляции и наследования. Полиморфизм это греческое слово, означающее "наличие многих форм". Это понятие имеет два различающихся аспекта. 1.Во время выполнения объекты производного класса могут рассматриваться как объекты базового класса в таких местах как параметры метода и коллекции массивов. При этом объявленный тип объекта больше не идентичен его типу времени выполнения. 2.Базовые классы могут определять и реализовывать виртуальные методы, а производные классы могут переопределять их. Это означает, что они предоставляют свои собственные определения и реализацию. Во время выполнения, когда клиентский код вызывает метод, среда CLR ищет тип времени выполнения объекта и вызывает это переопределение виртуального метода. Таким образом, в исходном коде можно вызвать метод в базовом классе и вызвать выполнение метода с версией производного класса.

Пусть имеется приложение рисования, которое дает возможность пользователю создавать на поверхности рисования различные формы. Во время компиляции неизвестно, какие конкретные формы будет создавать пользователь. Но приложение должно учитывать все различные типы создаваемых форм, и оно должно обновлять их в ответ на действия пользователя. 1.Создадим базовый класс, называемый Shape, и производные классы, например Rectangle, Circle и Triangle. 2.Определим в классе Shape виртуальный метод, называемый Draw, и переопределим его в каждом производном классе для рисования конкретной формы, которую представляет класс. 3.Создадим объект List и добавим в него круг, треугольник и прямоугольник. 4.Для обновления поверхности рисования используем цикл foreach для итерации списка и вызова метода Draw на каждом объекте Shape в списке. Хотя каждый объект в списке имеет объявленный тип Shape, будет вызываться именно тип времени выполнения (переопределенная версия метода в каждом производном классе).

public class Shape { // A few example members public int X { get; private set; } public int Y { get; private set; } public int Height { get; set; } public int Width { get; set; } // Virtual method public virtual void Draw() { Console.WriteLine("Performing base class drawing tasks"); } } class Circle : Shape { public override void Draw() { // Code to draw a circle... Console.WriteLine("Drawing a circle"); base.Draw(); } class Rectangle : Shape { public override void Draw() { // Code to draw a rectangle... Console.WriteLine("Drawing a rectangle"); base.Draw(); } class Triangle : Shape { public override void Draw() { // Code to draw a triangle... Console.WriteLine("Drawing a triangle"); base.Draw(); }

class Program { static void Main(string[] args) { // Polymorphism at work #1: a Rectangle, Triangle and Circle // can all be used whereever a Shape is expected. No cast is // required because an implicit conversion exists from a derived // class to its base class. System.Collections.Generic.List shapes = new System.Collections.Generic.List (); shapes.Add(new Rectangle()); shapes.Add(new Triangle()); shapes.Add(new Circle()); // Polymorphism at work #2: the virtual method Draw is // invoked on each of the derived classes, not the base class. foreach (Shape s in shapes) { s.Draw(); } // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } /* Output: Drawing a rectangle Performing base class drawing tasks Drawing a triangle Performing base class drawing tasks Drawing a circle Performing base class drawing tasks */

Прекращение наследования. sealed-спецификатор Принцип наследования допускает неограниченную глубину иерархии наследования. Производный класс, являющийся наследником базового класса, может в свою очередь сам оказаться в роли базового класса. Однако не всегда продолжение цепочки "предок-потомок" может оказаться целесообразным. Если при разработке класса возникла ситуация, при которой дальнейшее совершенствование и переопределение возможностей класса в деле решения специфических задач окажется нежелательным (сколько можно заниматься переопределением функций форматирования), класс может быть закрыт для дальнейшего наследования. Закрытие класса обеспечивается спецификатором sealed. При этом закрываться для наследования может как класс целиком, так и отдельные его члены.

Запечатанный класс Запечатанный класс не позволяет другим классам быть от него производными. При применении к классу, модификатор sealed запрещает другим классам наследовать от этого класса. В приведенном примере класс B наследует от класса A, но никакие классы не могут наследовать от класса B. Модификатор sealed можно использовать для метода или свойства, которое переопределяет виртуальный метод или свойство в базовом классе. Это позволяет классам наследовать от вашего класса, запрещая им при этом переопределять определенные виртуальные методы или свойства. class A {} sealed class B : A {}

class X { protected virtual void F() { Console.WriteLine("X.F"); } protected virtual void F2() { Console.WriteLine("X.F2"); } } class Y : X { sealed protected override void F() { Console.WriteLine("Y.F"); } protected override void F2() { Console.WriteLine("X.F3"); } } class Z : Y { // Attempting to override F causes compiler error CS0239. // protected override void F() { Console.WriteLine("C.F"); } // Overriding F2 is allowed. protected override void F2() { Console.WriteLine("Z.F2"); } } В следующем примере класс Z наследуется от класса Y, но Z не может переопределить виртуальную функцию F, которая объявлена в классе X и "запечатана" в классе Y.

Производный класс может прекратить наследование, объявив переопределение как sealed. public class A { public virtual void DoWork() { } } public class B : A { public override void DoWork() { } } public class C : B { public sealed override void DoWork() { } } Метод DoWork больше не является виртуальным для любого класса, производного от класса C.

Запечатанные методы могут быть заменены производными классами с помощью ключевого слова new public class D : C { public new void DoWork() { } }

Синтаксис объявления абстрактной функции предполагает использование ключевого слова abstract и полное отсутствие тела. Объявление абстрактной функции завершается точкой с запятой. Класс, содержащий вхождения абстрактных (хотя бы одной) функций, также должен содержать в заголовке объявления спецификатор abstract. В производном классе соответствующая переопределяемая абстрактная функция обязательно должна включать в заголовок функции спецификатор override. Его назначение – явное указание факта переопределения абстрактной функции. Абстрактный класс фактически содержит объявления нереализованных функций базового класса. На основе абстрактного класса невозможно определить объекты. Попытка создания соответствующего объекта представителя абстрактного класса приводит к ошибке, поскольку в классе не определены алгоритмы, определяющие поведение объекта

abstract class ShapesClass { abstract public int Area(); } class Square : ShapesClass { int side = 0; public Square(int n) { side = n; } public override int Area() { return side * side; } static void Main() { Square sq = new Square(12); Console.WriteLine("Area of the square = {0}", sq.Area()); } // Output: Area of the square = 144

Чтобы предотвратить переопределение производных классов при определении новых методов или свойств, не назначайте их в качестве виртуальных (virtual). Нельзя использовать модификатор abstract с sealed-классом, поскольку абстрактный класс должен наследоваться классом, реализующим абстрактные методы или свойства. При применении sealed модификатора к методу или свойству его необходимо всегда использовать с override. Поскольку структуры неявно запечатаны, их нельзя наследовать.

Полное квалифицированное имя В следующем примере и базовый BC-, и производный DC-классы используют одно и то же имя для обозначения объявляемого в обоих классах члена типа int. new - модификатор подчеркивает факт недоступности члена x базового класса в производном классе. Однако из производного класса все-таки можно обратиться к переопределенному полю базового класса с использованием полного квалифицированного имени: using System; public class BC { public static int x = 55; public static int y = 22; } public class DC : BC { new public static int x = 100; // Переопределили член базового класса public static void Main( ) { Console.WriteLine(x); // Доступ к переопределенному члену x: Console.WriteLine(BC.x); // Доступ к члену базового класса x: Console.WriteLine(y); // Доступ к члену y: }

То же с классами. В производном классе DC переопределяется вложенный класс C. В рамках производного класса легко можно создать объект представитель вложенного переопределенного класса C. Для создания аналогичного объекта представителя базового класса необходимо использовать полное квалифицированное имя: using System; public class BC { public class C { public int x = 200; public int y; } } public class DC:BC { // Вложенный класс базового класса скрывается new public class C { public int x = 100; public int y; public int z; } public static void Main() { // Из производного класса виден переопределенный вложенный класс: C s1 = new C(); // Полное имя используется для доступа к классу, вложенному в базовый: BC.C s2 = new BC.C(); Console.WriteLine(s1.x); Console.WriteLine(s2.x); }

Класс может содержать объявления следующих членов: Конструкторы Деструкторы Константы неизменные значения, известные во время компиляции и неизменяемые на протяжении времени существования программы. Константы объявляются с модификатором const. Поля Поле - это переменная любого типа, которая объявлена непосредственно в классе или структуре. Методы Свойства Индексаторы Операторы События Делегаты Классы Интерфейсы Структуры

Конструкторы Деструкторы Константы Поля Методы Свойства Индексаторы Операторы События Делегаты Классы Интерфейсы СтруктурыКонструкторы Каждый раз, когда создается класс или структура, вызывается конструктор. Класс или структура может иметь несколько конструкторов, принимающих различные аргументы. Конструкторы вызываются при создании экземпляров объекта с помощью оператора new public class Taxi { public bool isInitialized; public Taxi() { isInitialized = true; } // Конструктор } class TestTaxi { static void Main() { Taxi t = new Taxi(); Console.WriteLine(t.isInitialized); } Этот конструктор экземпляра вызывается каждый раз при создании объекта на базе класса. Такой конструктор без аргументов называется конструктором по умолчанию.

Если класс не содержит конструктор, автоматически создается конструктор по умолчанию и для инициализации полей объекта используются значения по умолчанию. Например, int инициализируется значением 0. Эти значения можно посмотреть в Таблице.

Тип значенияЗначение по умолчанию boolfalse byte0 char'\0' decimal0,0M double0,0D enumЗначение, созданное выражением (E)0, где E идентификатор перечисления. float0,0F int (Целочисленное значение) 0 long0L sbyte0 short0 structЗначение, полученное путем установки значений по умолчанию для полей типов значений и установки значения null для полей ссылочных типов. uint0 ulong0 ushort0

В этом примере класс Person не имеет конструкторов, поэтому автоматически предоставляется конструктор по умолчанию, а все поля инициализируются со значениями по умолчанию. public class Person { public int age; public string name; } class TestPerson { static void Main() { Person person = new Person(); Console.WriteLine("Name: {0}, Age: {1}", person.name, person.age); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } // Output: Name:, Age: 0

При наследовании конструкторы и деструкторы не наследуются. Передача управления конструктору базового класса осуществляется посредством конструкции :base(...) После ключевого слова base в скобках располагается список значений параметров конструктора базового класса. В следующем примере демонстрируется использование инициализатора базового класса. Класс Circle является производным от основного класса Shape, а класс Cylinder является производным от класса Circle. Конструктор каждого производного класса использует инициализатор своего базового класса.

abstract class Shape { public const double pi = Math.PI; protected double x, y; public Shape(double x, double y) { this.x = x; this.y = y; } public abstract double Area(); } class Circle : Shape { public Circle(double radius) : base(radius, 0) { } public override double Area() { return pi * x * x; } } class Cylinder : Circle { public Cylinder(double radius, double height) : base(radius) { y = height; } public override double Area() { return (2 * base.Area()) + (2 * pi * x * y); } } class TestShapes { static void Main() { double radius = 2.5; double height = 3.0; Circle ring = new Circle(radius); Cylinder tube = new Cylinder(radius, height); Console.WriteLine("Area of the circle = {0:F2}", ring.Area()); Console.WriteLine("Area of the cylinder = {0:F2}", tube.Area()); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } /* Output: Area of the circle = Area of the cylinder = */

Статические классы и структуры также могут иметь конструкторы. Статический конструктор используется для инициализации любых статических данных или для выполнения определенного действия, которое требуется выполнить только один раз. Он вызывается автоматически перед созданием первого экземпляра или ссылкой на какие-либо статические члены. Статический конструктор не принимает модификаторы доступа и не имеет параметров. Статический конструктор нельзя вызывать напрямую. class SimpleClass { static readonly long baseline; static SimpleClass() { baseline = DateTime.Now.Ticks; } }

Конструкторы Деструкторы Константы Поля Методы Свойства Индексаторы Операторы События Делегаты Классы Интерфейсы СтруктурыДеструкторы Деструкторы используются для уничтожения экземпляров классов. В структурах определение деструкторов невозможно. Они применяются только в классах. Класс может иметь только один деструктор. Деструкторы не могут наследоваться или перегружаться. Деструкторы невозможно вызвать. Они запускаются автоматически. Деструктор не принимает модификаторы и не имеет параметров. class Car { ~Car() // destructor { // cleanup statements... }

Деструктор неявным образом вызывает метод Finalize для базового класса объекта. Следовательно, предыдущий код деструктора неявным образом преобразуется в следующий код: protected override void Finalize() { try { // Cleanup statements... } finally { base.Finalize(); } Это означает, что метод Finalize вызывается рекурсивно для всех экземпляров цепочки наследования начиная с самого дальнего и заканчивая самым первым. Пустые деструкторы использовать не следует. Если класс содержит деструктор, то в очереди метода Finalize создается запись. При вызове деструктора вызывается сборщик мусора, выполняющий обработку очереди. Если деструктор пустой, это приводит только к ненужному снижению производительности.

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

В целом, язык C# не требует управления памятью в той степени, в какой это требуется в случае разработки кода на С++. Это связано с тем, что сборщик мусора платформы.NET Framework неявным образом управляет выделением и высвобождением памяти для объектов. Если объект требует уничтожения, то сборщик мусора запускает выполнение метода Finalize этого объекта. Однако при инкапсуляции приложением неуправляемых ресурсов, таких как сетевые подключения, для высвобождения этих ресурсов следует использовать деструкторы. В случае, когда приложением используется ценный внешний ресурс, также рекомендуется обеспечить способ высвобождения этого ресурса явным образом, прежде чем сборщик мусора освободит этот объект. Это выполняется путем реализации метода Dispose интерфейса IDisposable, который выполняет необходимую очистку для объекта. Это может значительно повысить производительность приложения.

В следующем примере создаются три класса, образующих цепочку наследования. Класс First является базовым, класс Second является производным от класса First, а класс Third является производным от класса Second. Все три класса имеют деструкторы. В методе Main( ) создается экземпляр самого дальнего в цепочке наследования класса. При выполнении программы обратите внимание, что происходит автоматический вызов деструкторов всех трех классов по порядку от самого дальнего до первого в цепочке наследования.

class First { ~First() { System.Diagnostics.Trace.WriteLine("First's destructor is called."); } } class Second : First { ~Second() { System.Diagnostics.Trace.WriteLine("Second's destructor is called."); } } class Third : Second { ~Third() { System.Diagnostics.Trace.WriteLine("Third's destructor is called."); } } Class TestDestructors { static void Main() { Third t = new Third(); } /* Output (to VS Output Window): Third's destructor is called. Second's destructor is called. First's destructor is called. */

Методы Метод Метод представляет собой блок кода, содержащий набор инструкций. Программа инициирует выполнение операторов, вызывая метод и задавая необходимые аргументы метода. В C# все инструкции выполняются в контексте метода. Метод Main( ) является точкой входа для каждого приложения C#, и вызывается он средой CLR при запуске программы. Методы объявляются в классе или в структуре путем указания уровня доступа, например public или private, необязательных модификаторов, например abstract или sealed, возвращаемого значения, имени метода и списка параметров этого метода. Все вместе эти элементы образуют сигнатуру метода. Следующий класс содержит два метода. public class Motorcycle { public void StartEngine() {/* Method statements here */ } protected void AddGas(int gallons) { /* Method statements here */ } } Конструкторы Деструкторы Константы Поля Методы Свойства Индексаторы Операторы События Делегаты Классы Интерфейсы Структуры

В следующем примере определяется открытый класс, содержащий одно поле, метод и два конструктора. public class Person { public string name; // Поле public Person() // Конструктор без аргументов { name = "unknown"; } public Person(string nm) // Конструктор с одним аргументом { name = nm; } public void SetName(string newName) // Метод { name = newName; }

Доступ к методам Вызов метода объекта очень похож на обращение к полю. После имени объекта ставится точка, затем имя метода и скобки. В скобках перечисляются аргументы, разделенные запятыми. class TestPerson { static void Main() { Person person1 = new Person(); // Конструктор без параметров. Console.WriteLine(person1.name); // Обращение Person person2 = new Person("Sarah Jones"); // Конструктор с параметром. Console.WriteLine(person2.name); person1.SetName("John Smith"); // Вызов метода Console.WriteLine(person1.name); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } // Output: // unknown // John Smith // Sarah Jones

Возвращаемые значения Методы могут возвращать значения вызывающим их объектам. Если тип возвращаемого значения, указываемый перед именем метода, не равен void, для возвращения значения используется ключевое слово return. В результате выполнения инструкции с ключевым словом return, после которого указано значение нужного типа, вызвавшему метод объекту будет возвращено это значение. Кроме того, ключевое слово return останавливает выполнение метода. Если тип возвращаемого значения void, инструкцию return без значения все равно можно использовать для завершения выполнения метода. Если ключевое слово return отсутствует, выполнение метода завершится, когда будет достигнут конец его блока кода. class SimpleMath { public int AddTwoNumbers(int number1, int number2) { return number1 + number2; } public int SquareANumber(int number) { return number * number; } }

Передача параметров по ссылке и по значению По умолчанию при передаче методу по значению передается копия объекта, а не сам объект. Поэтому изменения в методе не оказывают влияния на исходную копию. Когда объект ссылочного типа передается методу, передается ссылка на этот объект. Создадим ссылочный тип с помощью ключевого слова class, как показано в следующем примере. public class SampleRefType { public int value; }

public static void TestRefType() { SampleRefType rt = new SampleRefType(); rt.value = 44; ModifyObject(rt); Console.WriteLine(rt.value); } static void ModifyObject(SampleRefType obj) { obj.value = 33; } В примере выполняются передача аргумента ссылочного типа. Метод TestRefType напечатает 33. Для передачи по значению ссылочного типа можно использовать ключевое слово ref.

Свойства (Properties) Свойства объединяют функции полей и методов. Для объекта, использующего какой-либо объект, свойство является полем, поэтому для доступа к свойству требуется тот же синтаксис, что и для поля. При реализации класса свойство является одним или двумя блоками кода, представляющими метод доступа get и/или метод доступа set. Свойства можно использовать, как если бы они являлись открытыми членами данных, хотя в действительности они являются специальными методами, называемыми методами доступа. Конструкторы Деструкторы Константы Поля Методы Свойства Индексаторы Операторы События Делегаты Классы Интерфейсы Структуры

Блок кода для метода доступа get выполняется, когда осуществляется чтение свойства; Блок кода для метода доступа set выполняется, когда свойству присваивается новое значение. Свойство без метода доступа set считается доступным только для чтения. В нем используется неявный параметр value, тип которого соответствует типу свойства. Свойство без метода доступа get считается доступным только для записи. Свойство с обоими методами доступа доступно для чтения и для записи.

class TimePeriod { private double seconds; public double Hours { get { return seconds / 3600; } set { seconds = value * 3600; } } class Program { static void Main() { TimePeriod t = new TimePeriod(); t.Hours = 24; // Вызывается свойство set System.Console.WriteLine("Time in hours: " + t.Hours); // Cвойство get } // Output: Time in hours: 24 В данном примере, класс TimePeriod хранит сведения о периоде времени в секундах, но свойство с именем Hours позволяет клиенту задать время в часах. Методы доступа для свойства Hours выполняют преобразование между часами и секундами. Ключевое слово value используется для определения значения, присваиваемого методом доступа set

Индексаторы (Indexers) Индексаторы позволяют индексировать экземпляры класса или структуры так же, как массивы. Индексаторы напоминают свойства, но их методы доступа принимают параметры. В следующем примере определяется универсальный класс и в качестве средств присвоения и извлечения значений создаются простые методы доступа get и set. Класс Program создает экземпляр этого класса для хранения строк. Конструкторы Деструкторы Константы Поля Методы Свойства Индексаторы Операторы События Делегаты Классы Интерфейсы Структуры

class SampleCollection { private T[ ] arr = new T[100]; public T this[int i] // Определяет индексатор, который позволяет клиентскому коду использовать скобки [ ] { get { return arr[i]; } set { arr[i] = value; } } class Program { static void Main(string[] args) { SampleCollection stringCollection = new SampleCollection (); stringCollection[0] = "Hello, World"; // Используются [ ] System.Console.WriteLine(stringCollection[0]); } // Output: // Hello, World.

Перегруженные операции Перегрузка операций строится на основе общедоступных (public) статических (вызываемых от имени класса) функций-членов с использованием ключевого слова operator. +, –, !, ~, ++, ––, true, falseУнарные символы операций, допускающие перегрузку true и false также являются операциями +, –, *, /, %, &, |, ^, >Бинарные символы операций, допускающие перегрузку ==, !=,, =Операции сравнения перегружаются &&, ||Условные логические операции моделируются с использованием ранее переопределенных операций & и | [ ][ ]Операции доступа к элементам массивов моделируются за счет индексаторов ( )( )Операции преобразования реализуются с использованием ключевых слов implicit и explicit +=, –=, *=, /=, %=, &=, =, ^=, >= Операции не перегружаются, по причине невозможности перегрузки операции присвоения =,., ?:, –>, new, is, sizeof, typeof Операции, не подлежащие перегрузке

При этом при перегрузке операций для любого нового типа выполняются следующие правила: префиксные операции ++ и –– перегружаются парами; операции сравнения перегружаются парами: если перегружается операция ==, также должна перегружаться операция !=. То же самое относится к парам, =. Для перегрузки оператора в пользовательском классе нужно создать метод в классе с правильной сигнатурой. Метод нужно назвать "operator X", где X имя или символ перегружаемого оператора. Унарные операторы имеют один параметр, а бинарные два. В каждом случае один параметр должен быть такого же типа, как класс или структура, объявившие оператор, как показано в следующем примере. public static Complex operator +(Complex c1, Complex c2)

Унарные операторные функции using System; using System.Collections.Generic; using System.Text; namespace operations { // Для моделирования унарных постфиксных и префиксных операций может // потребоваться модификация структуры класса: объявляются дополнительные // переменные для сохранения значений координат. public class Point2D { public float x, y; float xTmp, yTmp; public Point2D( ) { x = 0.0; y = 0.0; xTmp = 0.0; yTmp = 0.0; } public static Point2D operator ++(Point2D par) { par.x = (par.xTmp)++; // Фактическим координатам присваиваются старые значения. par.y = (par.yTmp)++; return par; } public static Point2D operator --(Point2D par) { par.x = --(par.xTmp); // Фактическим координатам присваиваются новые значения. par.y = --(par.yTmp); return par; }

class Program { static void Main(string[ ] args) { Point2D p = new Point2D( ); int i; // унарный плюс всегда постфиксный, а унарный минус всегда префиксный. for (i = 0; i < 10; i++) { p++; Console.WriteLine("{0:F3},{1:F3}",p.x, p.y); } Console.WriteLine("============================================"); for (i = 0; i < 10; i++) { ++p; Console.WriteLine("{0:F3},{1:F3}", p.x, p.y); } Console.WriteLine("============================================"); for (i = 0; i < 10; i++) { p--; Console.WriteLine("{0:F3},{1:F3}", p.x, p.y); } Console.WriteLine("============================================"); for (i = 0; i < 10; i++) { --p; Console.WriteLine("{0:F3},{1:F3}", p.x, p.y); } }

Бинарные операции Бинарные операции обязаны возвращать значения. public static Point2D operator + (Point2D par1, Point2D par2) { return new Point2D(par1.x+par2.x,par1.y+par2.y); } Реализуется алгоритм "сложения" значения типа Point2D со значением типа float. От перемены мест слагаемых сумма НЕ ИЗМЕНЯЕТСЯ. Однако эта особенность нашей операторной функции "сложения" (операции "сложения") должна быть прописана программистом. В результате получаем ПАРУ операторных функций, которые отличаются списками параметров. // Point2D + float public static Point2D operator + (Point2D par1, float val) { return new Point2D(par1.x+val,par1.y+val); } // float + Point2D public static Point2D operator + (float val, Point2D par1) { return new Point2D(val+par1.x,val+par1.y); }

А вот применение этих функций. Внешнее сходство выражений вызова операторных функций с обычными выражениями очевидно....p1 + p p p Операции сравнения реализуются по аналогичной схеме. Хотя не существует никаких ограничений на тип возвращаемого значения, в силу специфики применения (обычно в условных выражениях операторов управления) операций сравнения все же имеет смысл определять их как операторные функции, возвращающие значения true и false: public static bool operator == (myPoint2D par1, myPoint2D par2) { if ((par1.x).Equals(par2.x) && (par1.y).Equals(par2.y)) return true; else return false; } public static bool operator != (myPoint2D par1, myPoint2D par2) { if (!(par1.x).Equals(par2.x) || !(par1.y).Equals(par2.y)) return true; else return false; }

Перегрузка булевских операторов true и false В известном мультфильме о Винни Пухе и Пятачке, Винни делает заключение относительно НЕПРАВИЛЬНОСТИ пчел. Очевидно, что по его представлению объекты представители ДАННОГО класса пчел НЕ удовлетворяют некоторому критерию. В программе можно поинтересоваться непосредственно значением некоторого поля объекта: Point2D p1 = new Point2D(GetVal(), GetVal()); :::::::::: if ((p1.x).Equals(125)) {/*...*/} // Это логическое выражение! Так почему же не спросить об этом у объекта напрямую? В классе может быть объявлена операция (операторная функция) true, которая возвращает значение true типа bool для обозначения факта true и возвращает false в противном случае

Классы, включающие объявления подобных операций (операторных функций), могут быть использованы в структуре операторов if, do, while, for в качестве условных выражений. При этом, если в классе была определена операция true, в том же классе должна быть объявлена операция false: Перегрузка булевских операторов. Это ПАРНЫЕ операторы. Критерии ИСТИННОСТИ могут быть самые разные. В частности, степень удаления от точки с координатами (0,0). public static bool operator true (Point2D par) { if (Math.Sqrt(par.x*par.x + par.y*par.y) < 10.0) return true; else return false; } public static bool operator false (Point2D par) { double r = (Math.Sqrt(par.x*par.x + par.y*par.y)); if (r > 10.0 || r.Equals(10.0)) return false; else return true; }

Определение операций конъюнкция & и дизъюнкция | После того как объявлены операторы true и false, могут быть объявлены операторные функции конъюнкция и дизъюнкция. Эти функции работают по следующему принципу: после оценки истинности (критерий оценки задается при объявлении операций true и false) операндов конъюнкции или дизъюнкции функция возвращает ссылку на один из операндов либо на вновь создаваемый в функции объект; в соответствии с ранее объявленными операциями true и false, операторные конъюнкция и дизъюнкция возвращают логическое значение, соответствующее отобранному или вновь созданному объекту:

public static Point2D operator | (Point2D par1, Point2D par2) { if (par1) return par1; // Определить "правильность" объекта par1 можем! if (par2) return par2; // Определить "правильность" объекта par2 можем! return new Point2D(10.0F, 10.0F); // Вернули ссылку на новый "неправильный" объект. } public static Point2D operator & (Point2D par1, Point2D par2) { if (par1 && par2) return par1; // Вернули ссылку на один из "правильных" // объектов. else return new Point2D(10.0F, 10.0F); // Вернули ссылку на новый "неправильный" объект. } Выражение вызова операторной функции "дизъюнкция" имеет вид if (p0 | p1) Console.WriteLine("true!");

Определение операций || и && Эти операции сводятся к ранее объявленным операторным функциям. Обозначим символом T тип, в котором была объявлена данная операторная функция. Если при этом операнды операций && или || являются операндами типа T и для них были объявлены соответствующие операторные функции operator &( ) и/или operator |( ), то для успешной эмуляции операций && или || должны выполняться следующие условия: тип возвращаемого значения и типы каждого из параметров данной операторной функции должны быть типа T. Операторные функции operator & и operator |, определенные на множестве операндов типа T, должны возвращать результирующее значение типа T; к результирующему значению применяется объявленная в классе T операторная функция operator true (operator false).

При этом приобретают смысл операции && или ||. Их значение вычисляется в результате комбинации операторных функций operator true( ) или operator false() со следующими операторными функциями: 1. Операция x && y представляется в виде выражения, построенного на основе трехместной операции T.false(x)? x: T.&(x, y), где T.false(x) является выражением вызова объявленной в классе операторной функции false, а T.&(x, y) – выражением вызова объявленной в классе T операторной функции &. Таким образом, сначала определяется "истинность" операнда x, и если значением соответствующей операторной функции является ложь, результатом операции оказывается значение, вычисленное для x. В противном случае определяется "истинность" операнда y, и результирующее значение определяется как КОНЪЮНКЦИЯ истинностных значений операндов x и y.

2. Операция x || y представляется в виде выражения, построенного на основе трехместной операции T.true(x)? x: T.|(x, y), где T.true(x) является выражением вызова объявленной в классе операторной функции, а T.|(x, y) – выражением вызова объявленной в классе T операторной функции |. Таким образом, сначала определяется "истинность" операнда x, и если значением соответствующей операторной функции является истина, результатом операции оказывается значение, вычисленное для x. В противном случае определяется "истинность" операнда y, и результирующее значение определяется как ДИЗЪЮНКЦИЯ истинностных значений операндов x и y. При этом в обоих случаях "истинностное" значение x вычисляется один раз, а значение выражения, представленного операндом y, не вычисляется вообще либо определяется один раз.

Преобразования явные и неявные explicit и implicit Возвращаемся к проблеме преобразования значений одного типа к другому. И если для элементарных типов проблема преобразования решается (с известными ограничениями, связанными с "опасными" и "безопасными" преобразованиями), то для вновь объявляемых типов алгоритмы преобразования должны реализовываться разработчиками этих классов. Логика построения явных и неявных преобразователей достаточно проста. Программист самостоятельно принимает решение относительно того: каковым должен быть алгоритм преобразования; будет ли этот алгоритм выполняться неявно или необходимо будет явным образом указывать на соответствующее преобразование. Ниже рассматривается пример, содержащий объявления классов Point2D и Point3D. В классах предусмотрены алгоритмы преобразования значений от одного типа к другому, которые активизируются при выполнении операций присвоения:

Операторная функция, в которой реализуется алгоритм преобразования значения типа осуществляется с ЯВНЫМ указанием необходимости преобразования. Принятие решения относительно присутствия в объявлении ключевого слова explicit вместо implicit оправдывается тем, что это преобразование сопровождается потерей информации. Существует мнение, что об этом обстоятельстве программисту следует напоминать всякий раз, когда он в программном коде собирается применить данное преобразование.

using System; // Объявления классов. // Операторная функция, в которой реализуется алгоритм преобразования // значения типа Point2D в значение типа Point3D. Это преобразование осуществляется НЕЯВНО. class Point3D { public int x,y,z; public Point3D() { x = 0; y = 0; z = 0; } public Point3D(int xKey, int yKey, int zKey) { x = xKey; y = yKey; z = zKey; } public static implicit operator Point3D(Point2D p2d) { Point3D p3d = new Point3D(); p3d.x = p2d.x; p3d.y = p2d.y; p3d.z = 0; return p3d; } // Операторная функция, в которой реализуется алгоритм преобразования // значения типа Point3D в значение типа Point2D. Это преобразование // осуществляется с ЯВНЫМ указанием необходимости преобразования. class Point2D { public int x,y; public Point2D() { x = 0; y = 0; } public Point2D(int xKey, int yKey) { x = xKey; y = yKey; } public static explicit operator Point2D(Point3D p3d) { Point2D p2d = new Point2D(); p2d.x = p3d.x; p2d.y = p3d.y; return p2d; } }

// Тестовый класс. class TestClass { static void Main(string[ ] args) { Point2D p2d = new Point2D(125,125); Point3D p3d; // Сейчас это только ссылка! // Этой ссылке присваивается значение в результате // НЕЯВНОГО преобразования значения типа Point2D к типу Point3D p3d = p2d; // Изменили значения полей объекта. p3d.x = p3d.x*2; p3d.y = p3d.y*2; p3d.z = 125; // Главное – появилась новая информация, которая будет потеряна в случае присвоения // значения типа Point3D значению типа Point2D. Ключевое слово explicit в объявлении // соответствующего метода преобразования вынуждает программиста подтверждать, // что он в курсе возможных последствий этого преобразования. p2d = (Point2D)p3d; }