В рассмотренном выше примере класс 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#.