Интерфейсы и структуры
Стандартные интерфейсы .Net
В библиотеке классов .Net определено множество стандартных интерфейсов, задающих желаемую функциональность объектов. Например, интерфейс IComparable задает метод сравнения объектов по принципу больше и меньше, что позволяет переопределить соответствующие операции в рамках класса, наследующего интерфейс IComparable. Реализация интерфейсов IEnumerable и IEnumerator дает возможность просматривать содержимое объекта с помощью оператора foreach.
Можно создавать собственные классы, реализующие стандартные интерфейсы, что позволит использовать объекты этих классов стандартными способами.
Более подробно рассмотрим стандартный интерфейс IComparable.
Интерфейс IComparable определен в пространстве имен System и содержит единственный метод CompareTo, возвращающий результат сравнения двух объектов - текущего и переданного ему в качестве параметра:
interface IComparable { int CompareTo(object obj); }
Реализация данного метода должна возвращать:
- 0 - если текущий объект и параметр равны;
- отрицательное число, если текущий объект меньше параметра;
- положительное число, если текущий объект больше параметра.
В качестве примера рассмотрим реализацию классом DemoPoint стандартного интерфейса IComparable.
// класс DemoPoint реализует стандартный интерфейс IComparable class DemoPoint:IComparable { protected int x; protected int y; public DemoPoint ( int x, int y) { this.x=x; this.y=y; } public void Show() { Console.WriteLine("точка на плоскости: ({0}, {1})",x, y); } public double Dlina() { return Math.Sqrt(x*x+y*y); } //реализация метода CompareTo public int CompareTo (object obj) { DemoPoint b=(DemoPoint) obj; //преобразуем к типу DemoPoint //определяем критерии сравнения текущего объекта с параметром в // зависимости от удаленности точки от начала координат if (this.Dlina()==b.Dlina()) return 0; else if (this.Dlina()>b.Dlina()) return 1; else return -1; } } class Program { static void Main() { //создаем массив ссылок DemoPoint []a =new DemoPoint[4]; a[0]=new DemoPoint(5,-1); a[1]=new DemoPoint(-3, 3); a[2]=new DemoPoint(3,4); a[3]= new DemoPoint(0,1); //сортируем массив точек, при этом в качестве критерия сортировки будет //использоваться собственная реализация метода CompareTo Array.Sort(a); Console.WriteLine(); foreach (DemoPoint x in a) { x.Show(); Console.WriteLine("Dlina={0:f2} ",x.Dlina()); } } }
Обратите внимание на то, что во время реализации метода CompareTo в качестве параметра передавалась ссылка на объект типа object. Напомним, что класс object является корневым классом для всех остальных в С#. Поэтому он может ссылаться на объект любого типа. Но чтобы потом получить доступ к членам объекта произвольного класса, нужно выполнить приведение типов.
Используя собственную реализацию метода CompareTo можно перегрузить операции отношения. Напомним, что операции отношения должны перегружаться парами: < и >, <= и >=, == и !=.
В следующем примере для класса DemoPoint перегрузим операции == и != таким образом, чтобы при сравнении двух объектов возвращалось значение true, если точки находятся на равном удалении от начала координат, в противном случае - false.
class DemoPoint:IComparable { protected int x; protected int y; public DemoPoint ( int x, int y) { this.x=x; this.y=y; } public void Show() { Console.WriteLine("точка на плоскости: ({0}, {1})",x, y); } public double Dlina() { return Math.Sqrt(x*x+y*y); } public int CompareTo (object obj) { DemoPoint b=(DemoPoint) obj; if (this.Dlina()==b.Dlina()) return 0; else if (this.Dlina()>b.Dlina()) return 1; else return -1; } public static bool operator ==(DemoPoint a, DemoPoint b) { return (a.CompareTo(b)==0); } public static bool operator !=(DemoPoint a, DemoPoint b) { return (a.CompareTo(b)!=0); } } class Program { static void Main() { DemoPoint a =new DemoPoint(-3,0); DemoPoint b=new DemoPoint(0,3); if (a==b) Console.WriteLine("равно удалены от начала координат"); else Console.WriteLine("не равно удалены от начала координат"); } }
- > и <, так чтобы при сравнении двух объектов возвращалось значение true, если первая точка расположена дальше от начала координат, чем вторая, иначе false.
- >= и <=, так чтобы при сравнении двух объектов возвращалось значение true если первая точка расположена не ближе к началу координат, чем вторая, иначе false.
Структуры
Классы, как вы уже знаете, являются ссылочными данными. Это означает, что к экземплярам классов можно обратиться только через ссылку. В С# реализован тип данных, аналогичный классу, но который в отличие от классов является размерным типом. Таким типом является структура.
Так как структура является размерным типом, то экземпляр структуры хранит значение своих элементов, а не ссылки на них, и располагается в стеке данных, а не в куче. В связи с этим фактом структура не может участвовать в иерархии наследования, а может только реализовывать интерфейсы. Кроме того, структуре запрещено:
- определять конструктор по умолчанию, поскольку он определен неявно и присваивает всем своим элементам значения по умолчанию (нули соответствующего типа);
- определять деструктор, поскольку это бессмысленно.
[атрибуты][спецификаторы] struct имя_структуры [: интерфейсы] { тело_структуры }
Спецификаторы структуры имеют такой же смысл, как и для класса. Однако из спецификаторов доступа допускается использовать только public, internal и для вложенных структур еще и private. Кроме того, структуры не могут быть абстрактными, поскольку по умолчанию они бесплодны.
Интерфейсы, реализуемые структурой, перечисляются через запятую.
Тело структуры может содержать: константы, поля, конструкторы, методы, свойства, индексаторы, операторные методы, вложенные типы и события.
При описании структуры задавать значение по умолчанию можно только для статических полей. Остальным полям с помощью конструктора по умолчанию будут присвоены нули для полей размерных типов и null для полей ссылочных типов.
Параметр this интерпретируется как значение, поэтому его можно использовать для ссылок, но не для присваивания.
Так как структуры не могут участвовать в иерархии, то для ее членов недопустимо использовать спецификаторы protected и protected internal. Методы структур не могут быть абстрактными и виртуальными. А переопределяться могут только те методы, которые унаследованы от базового класса object.
Рассмотрим пример структуры SPoint, реализующей стандартный интерфейс IComparable. В данной структуре определены:
- поля x и у ;
- конструктор, в который передаются заданные координаты точки;
- метод Dlina, который позволяет вычислить расстояние от точки до начала координат;
- перегружен метод ToString базового класса object ;
- реализован метод CompareTo стандартного интерфейса IComparable ;
- перегружены операции == и !=.
//описание структуры struct SPoint: IComparable { public int x, y; public SPoint (int x, int y) { this.x=x; this.y=y; } public double Dlina() //метод { return Math.Sqrt(x*x+y*y); } public override string ToString() { return "("+x.ToString()+", "+y.ToString()+")"; } public int CompareTo (object obj) { SPoint b=(SPoint) obj; if (this.Dlina()==b.Dlina()) return 0; else if (this.Dlina()>b.Dlina()) return 1; else return -1; } public static bool operator ==(SPoint a, SPoint b) { return (a.CompareTo(b)==0); } public static bool operator !=(SPoint a, SPoint b) { return (a.CompareTo(b)!=0); } } class Program { static void Main() { //создание и заполнение массива структур SPoint []a =new SPoint[4]; a[0]=new SPoint(-3,0); a[1]=new SPoint(-0, 3); a[2]=new SPoint(3,4); a[3]= new SPoint(0,1); //сравнение двух структур if (a[0]==a[1]) Console.WriteLine("точки {0} и {1} равноудалены от начала координат\n", a[0].ToString(),a[1].ToString()); else Console.WriteLine("точки {0} и {1} не равноудалены от начала координат\n", a[0].ToString(),a[1].ToString()); Array.Sort(a); //сортировка массива структур //просмотр массива структур foreach (SPoint x in a) { Console.WriteLine("Точка: "+x.ToString()); Console.WriteLine("удалена от начала координат на расстояние равное: "+x.Dlina()); Console.WriteLine(); } } }
Как видно из примера, экземпляр структуры, как и экземпляр класса, создаются с помощью оператора new, но это не обязательно. Если оператор new не используется, то структура все равно создается, но не инициализируется. По этой причине следующая последовательность команд будет ошибочна:
SPoint one; Console.WriteLine(one.ToString());
Если при объявлении структуры не был вызван конструктор, то поля нужно инициализировать вручную:
SPoint one; one.x=100; one.y=100; Console.WriteLine(one.ToString());
Так как структуры являются размерными типами, то присваивание одной структуры другой создает копию экземпляра структуры. Этот факт является важным отличием структуры от класса. В результате выполнения следующего фрагмента программы мы убедимся, что структуры one и two не зависят друг от друга.
SPoint one=new SPoint(); //вызов конструктора по умолчанию SPoint two=new SPoint(1,1); //вызов собственного конструктора Console.WriteLine(one.ToString()); Console.WriteLine(two.ToString()); one=two; one.x=100; Console.WriteLine(one.ToString()); Сonsole.WriteLine(two.ToString());
Как при решении практических задач выбрать, что использовать - класс или структуру? Если создаваемые типы данных содержат небольшое количество полей, с которыми удобнее работать как со значениями, а не как со ссылками, то расходы на выделение динамической памяти для небольших объектов снизят быстродействие программы, поэтому такие типы данных эффективнее реализовывать через структуры. Во всех остальных случаях эффективнее использовать классы. Однако передача структуры в методы по значению потребует и дополнительного времени, и дополнительной памяти для создания копии. В таких случаях эффективнее использовать классы.