В рассмотренном выше примере класс Figure является таким же «полноценным» классом, как Circle и Rectangle. Объект этого класса может быть создан, его методы могут быть вызваны, его поля доступны для чтения и записи. Однако класс Figure является только «оберткой» и практической надобности в создании объектов именно этого класса нет. Чтобы обезопасить дальнейшую разработку от возможных ошибок, связанных с созданием и использованием экземпляров класса Figure, в C# существует ключевое слово abstract. Ключевое слово abstract позволяет создавать классы и члены классов, которые являются неполными и должны быть реализованы в производном классе. Классы могут быть объявлены абстрактными путем помещения ключевого слова abstract перед определением класса.
abstract class Figure
{
// Координаты
public double X = 0;
public double Y = 0;
// Метод расчета площади фигуры
public abstract double CalcArea();
}
Создавать экземпляры абстрактного класса нельзя. Назначение абстрактного класса заключается в предоставлении общего определения для базового класса, которое могут совместно использовать несколько производных классов. Например, в библиотеке классов может быть определен абстрактный класс, используемый в качестве параметра для многих ее функций, поэтому программисты, использующие эту библиотеку, должны задать свою реализацию этого класса, создав производный класс.
Абстрактные классы могут определять абстрактные методы. Для этого перед типом возвращаемого значения метода необходимо поместить ключевое слово abstract. Абстрактные методы не имеют реализации, поэтому определение такого метода заканчивается точкой с запятой вместо обычного блока метода. Классы, производные от абстрактного класса, должны реализовывать все абстрактные методы.
4.6. Работа со списками в C#
Классы для работы со списками, очередями, двоичными массивами, хэш-таблицами и словарями находятся в пространстве имен System.Collections.
Одним из наиболее широко используемых классов в этой коллекции является класс List. Он представляет из себя динамический список, т. е. мы можем добавлять в него элементы, удалять, искать и т. п. Списки применяются там, где количество элементов в коллекции наперед не известно. Списки выгодно отличаются от массивов тем, что по ходу выполнения программы их размер можно изменять в любую сторону. Кроме того, в этом generic-классе реализованы многие стандартные алгоритмы (сортировка, например).
Класс List - это generic класс. Это означает, что при объявлении экземпляра этого класса указывается некий конкретный тип, который и будет содержаться в списке.
Основные методы класс List перечислены в таблице 4.1.
Таблица 4.1. Основные методы класса List
| Имя | Описание |
| Add | Добавляет объект в конец коллекции List<T> |
| Clear | Удаляет все элементы из коллекции List<T> |
| Contains | Определяет, входит ли элемент в состав List<T> |
| CopyTo | Перегружен. Копирует список List<T> или его часть в массив |
| Exists | Определяет, содержит ли List<T> элементы, удовлетворяющие условиям указанного предиката |
| Find | Выполняет поиск элемента, удовлетворяющего условиям указанного предиката, и возвращает первое найденное вхождение в пределах всего списка List<T> |
| FindAll | Извлекает все элементы, удовлетворяющие условиям указанного предиката |
| Insert | Добавляет элемент в список List<T> в позиции с указанным индексом |
| LastIndexOf | Перегружен. Возвращает отсчитываемый от нуля индекс последнего вхождения значения в списке List<T> или в его части |
| Remove | Удаляет первое вхождение указанного объекта из коллекции List<T> |
| RemoveAll | Удаляет все элементы, удовлетворяющие условиям указанного предиката |
| RemoveAt | Удаляет элемент списка List<T> с указанным индексом |
| Reverse | Перегружен. Изменяет порядок элементов в списке List<T> или в его части на обратный |
| Sort | Перегружен. Сортирует элементы в списке List<T> или в его части |
| ToArray | Копирует элементы списка List<T> в новый массив |
| TrueForAll | Определяет, все ли элементы списка List<T> удовлетворяют условиям указанного предиката |
Пример использования класса List.
List<Figure> lf = new List<Figure>();
lf.Add(new Circle() {X=10,Y=10,R=100});
lf.Add(new Circle() {X=10,Y=10,R=120});
lf.Add(new Circle() {X=10,Y=10,R=130});
lf.Add(new Rectangle() {X=20,Y=20,A=50,B=60});
lf.Add(new Rectangle() {X=20,Y=20,A=60,B=50});
lf.Add(new Rectangle() {X=20,Y=20,A=30,B=30});
double sumArea = 0;
foreach (Figure f in lf)
{
sumArea += f.CalcArea();
}
Console.WriteLine("Площадь всех фигур в списке - " +
sumArea.ToString());
Console.ReadLine();
В приведенном примере на экран будет выведено
число – сумма площадей всех фигур в списке.
Важно отметить, что обращение к методу CalcArea происходит через ссылку на класс предок Figure и, таким образом, в примере в полной мере применяется принцип полиморфизма.
Класс System.Object
В С# все типы данных (как структурные, так и ссылочные) производятся от единого общего предка: класса System.Object. Класс System.Object определяет общее полиморфное поведение для всех типов данных в.NET. Во всех предыдущих примерах не было необходимости явно указывать класс System.Object в качестве базового - это подразумевается само собой. Однако нам ничто не мешает сделать это, явно указав, что класс производится от System.Object:
class HelloClass: System.Object
// либо class HelloClass: object
{... }
Как и в любом другом классе С#, в классе System.Object существует свой набор членов (табл.4.2). Учитывая, что некоторые члены определены как виртуальные, они могут быть замещены в определении производного класса:
// Самый верхний класс в иерархии классов.NET: System.Object
namespace System
{
public class Object
{
public Object();
public virtual Boolean Equals (Object obj);
public virtual Int32 GetHashCode();
public Type GetType();
public virtual String ToString();
…
}
}
Таблица 4.2. Назначение методов класса System.Object
| Метод | Назначение |
| Equals() | Метод сравнивает текущий объект с объектом obj, переданным в качестве параметра. По умолчанию этот метод возвращает «истинно» только тогда, когда сравниваемые сущности указывают на одну и ту же область в оперативной памяти. Поэтому этот метод в его исходной реализации предназначен только для сравнения объектов ссылочных типов, но не структурных. Для нормальной работы с объектами структурных типов этот метод необходимо заместить |
| GetHashCode() | Возвращает целочисленное значение, идентифицирующее конкретный экземпляр объекта данного типа |
| GetType() | Метод возвращает объект Туре(), полностью описывающий тот объект, из которого метод был вызван. Это метод идентификации времени выполнения, который предусмотрен во всех объектах |
| ToString() | Возвращает символьное представление объекта в формате <имя_пространства_имен>.<имя_класса> (такой формат носит также название "полностью определенного имени" - fully qualified name) |
Методы, которые типы данных наследуют от System.Object, во многих ситуациях исключительно полезны. Но обычно при создании своих собственных типов данных некоторые методы System.Object приходится замещать. Рассмотрим такое замещение на примере.
В качестве замещаемого метода выберем Object.ToString(). Мы хотим, чтобы этот метод возвращал не имя типа, а информацию о внутреннем состоянии объекта.
class Program
{
static void Main(string[] args)
{
Student s = new Student() {
FIO = "Петр Трофимов",
Age = 26
};
Console.WriteLine(s);
Console.ReadLine();
}
}
class Student
{
public string FIO;
public int Age;
public override string ToString()
{
return String.Format("{0}, возраст {1} лет",
FIO, Age);
}
}
Контрольные вопросы
1. Что такое конструктор класса? Какова его роль в программе?
2. Назовите основные средства реализации принципа инкапсуляции в C#.
3. Приведите пример реализации отношения наследования в C#.
4. Каковы необходимые условия реализации принципа полиморфизма?
5. Назовите основные средства реализации принципа полиморфизма в C#.
6. Поясните роль класса System.Object в программе C#.






