русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Самостоятельная работа


Дата добавления: 2013-12-23; просмотров: 958; Нарушение авторских прав


Используя дополнительную литературу и Интернет, рассмотрите следующие темы:

  1. Идентификация типов во время выполнения программы: использование операторов is, as, typeof.
  2. Отражение и его использование для получения информации о методах, конструкторов типов, типов из сборок и автоматического распознавания типов.

Лекция 15. Интерфейсы и структуры

15.1. Пользовательские и стандартные интерфейсы

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

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

Синтаксис интерфейса:

[атрибуты] [спецификаторы] interface имя_интерфейса : [предки]{//объявление функциональных членов интерфейса без реализации… }

Для интерфейса могут быть указаны спецификаторы new, public, internal и private. Спецификатор new применяется для вложенных интерфейсов и имеет такой же смысл, как и соответствующий спецификатор метода класса. По умолчанию интерфейс доступен только из сборки, в которой он описан (internal).

Все функциональные члены интерфейса по умолчанию являются открытыми (public) и абстрактными (abstract), поэтому при описании метода указывается только типа возвращаемого им значения и сигнатуры.



В качестве функциональных членов в интерфейсе можно объявлять сигнатуры методов, свойств, индексаторов и событий (для Windows-приложений). Интерфейсы не могут содержать члены данных, конструкторы, деструкторы или операторные методы (методы, переопределяющие операции). Ни один член интерфейса не может быть объявлен статическим.

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

В качестве примера рассмотрим интерфейс IDemo и его реализацию для классов DemoPoint и DemoLine из предыдущей лекции:

//определение интерфейсаinterface IDemo { void Show(); //объявление метода double Dlina(); //объявление метода int X {get;} //объявление свойства, доступного только для чтения int this [int i]{get;set;} //объявление индексатора, доступного для чтения-записи } //класс DemoPoint наследует интерфейс IDemoclass DemoPoint:IDemo { 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 X //реализация свойства, объявленного в интерфейсе { get { return x; } } public int this [int i] //реализация индексатора, объявленного в интерфейсе { get { if (i==0) return x; else if (i==1) return y; else throw new Exception ("недопустимое значение индекса");} set { if (i==0) x=value; else if (i==1) y=value; else throw new Exception ("недопустимое значение индекса"); } } } //класс DemoShape наследует класс DemoPoint и интерфейс IDemoclass DemoShape : DemoPoint, IDemo{ protected int z; public DemoShape(int x, int y, int z):base(x, y) { this.z=z; } // реализация метода, объявленного в интерфейсе, с сокрытием одноименного метода из //базового класса public new void Show() { Console.WriteLine("точка в пространстве: ({0}, {1}, {2})", x, y, z); } // реализация метода, объявленного в интерфейсе, с сокрытием одноименного метода из //базового класса public new double Dlina() { return Math.Sqrt(x*x+y*y+z*z); } // реализация индексатора, объявленного в интерфейсе, с сокрытием одноименного // индексатора из базового класса public new int this [int i] { get { if (i==0) return x; else if (i==1) return y; else if (i==2) return z; else throw new Exception ("недопустимое значение индекса"); } set { if (i==0) x=value; else if (i==1) y=value; else if (i==2) z=value; else throw new Exception ("недопустимое значение индекса"); } } } class Program { static void Main() { //создание массива интерфейсных ссылок IDemo []a =new IDemo[4]; //заполнение массива a[0]=new DemoPoint(0,1); a[1]=new DemoPoint(-3, 0); a[2]=new DemoShape(3,4,0); a[3]= new DemoShape(0,5, 6); //просмотр массива foreach (IDemo x in a) { x.Show(); Console.WriteLine("Dlina={0:f2}",x.Dlina()); Console.WriteLine("x="+x.X); x[1]+=x[0]; Console.Write("новые координаты - "); x.Show(); Console.WriteLine(); } }}

Обратите внимание, что в интерфейсе IDemo было объявлено свойство Х, доступное только для чтения. Наследуя интерфейс IDemo, класс DemoPoint определил реализацию данного свойства. Класс DemoShape, наследуя класс DemoPoint и IDemo, не предложил собственной реализации свойства Х, поэтому при обращении к объекту типа DemoShape вызывается реализация данного свойства, определенная в базовом классе.

Задания.

  1. Добавьте в интерфейс IDemo свойство Y, которое позволит обращаться для чтения к значению поля у. Реализуйте работу с данным свойством в классах DemoPoint и DemoShape.
  2. Добавьте свойство Z для обращения к полю z класса DemoShape. Подумайте, куда именно нужно добавить определение данного свойства и почему.

15.2 Стандартные интерфейсы .Net

В библиотеке классов .Net определено множество стандартных интерфейсов, задающих желаемую функциональность объектов. Например, интерфейс IComparable задает метод сравнения объектов по принципу больше и меньше, что позволяет переопределить соответствующие операции в рамках класса, наследующего интерфейс IComparable. Реализация интерфейсов IEnumerable и IEnumerator дает возможность просматривать содержимое объекта с помощью оператора foreach.

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

Более подробно рассмотрим стандартный интерфейс IComparable.

Интерфейс IComparable определен в пространстве имен System и содержит единственный метод CompareTo, возвращающий результат сравнения двух объектов - текущего и переданного ему в качестве параметра:

interface IComparable{ int CompareTo(object obj);}

Реализация данного метода должна возвращать:

  1. 0 - если текущий объект и параметр равны;
  2. отрицательное число, если текущий объект меньше параметра;
  3. положительное число, если текущий объект больше параметра.

В качестве примера рассмотрим реализацию классом DemoPoint стандартного интерфейса IComparable.

// класс DemoPoint реализует стандартный интерфейс IComparableclass 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 так, чтобы метод Sort сортировал массив точек по убыванию расстояния между точкой и началом координат.

Обратите внимание на то, что во время реализации метода 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("не равно удалены от начала координат"); }}

Задание. Перегрузите в классе DemoPoint следущие пары операций:

  1. > и <, так чтобы при сравнении двух объектов возвращалось значение true, если первая точка расположена дальше от начала координат, чем вторая, иначе false.
  2. >= и <=, так чтобы при сравнении двух объектов возвращалось значение true если первая точка расположена не ближе к началу координат, чем вторая, иначе false.

15.3 Структуры

Классы, как вы уже знаете, являются ссылочными данными. Это означает, что к экземплярам классов можно обратиться только через ссылку. В С# реализован тип данных, аналогичный классу, но который в отличие от классов является размерным типом. Таким типом является структура.

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

  1. определять конструктор по умолчанию, поскольку он определен неявно и присваивает всем своим элементам значения по умолчанию (нули соответствующего типа);
  2. определять деструктор, поскольку это бессмысленно.

Синтаксис структуры:

[атрибуты][спецификаторы] struct имя_структуры [: интерфейсы]{ тело_структуры}

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

Интерфейсы, реализуемые структурой, перечисляются через запятую.

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

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

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

Так как структуры не могут участвовать в иерархии, то для ее членов недопустимо использовать спецификаторы protected и protected internal. Методы структур не могут быть абстрактными и виртуальными. А переопределяться могут только те методы, которые унаследованы от базового класса object.

Рассмотрим пример структуры SPoint, реализующей стандартный интерфейс IComparable. В данной структуре определены:

  1. поля x и у;
  2. конструктор, в который передаются заданные координаты точки;
  3. метод Dlina, который позволяет вычислить расстояние от точки до начала координат;
  4. перегружен метод ToString базового класса object;
  5. реализован метод CompareTo стандартного интерфейса IComparable;
  6. перегружены операции == и !=.
//описание структуры struct SPoint: IComparable { public int x, y; public SPoint (int x, int y) { this.x=a; 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());

Как при решении практических задач выбрать, что использовать - класс или структуру? Если создаваемые типы данных содержат небольшое количество полей, с которыми удобнее работать как со значениями, а не как со ссылками, то расходы на выделение динамической памяти для небольших объектов снизят быстродействие программы, поэтому такие типы данных эффективнее реализовывать через структуры. Во всех остальных случаях эффективнее использовать классы. Однако передача структуры в методы по значению потребует и дополнительного времени, и дополнительной памяти для создания копии. В таких случаях эффективнее использовать классы.

Самостоятельная работа

В пространстве имен System.Collection определено несколько интерфейсов, определяющих функциональность многих стандартных классов в С#, в том числе и коллекций (которые будет рассматриваться в следующем разделе). Рассмотрим более подробно интерфейсные коллекции, описание которых приведено в следующей таблице:

Интерфейс Описание
IEnumerator Содержит методы, которые позволяют поэлементно получать содержимое коллекции
IEnumerable Определяет метод GetEnumerator( ), который поддерживает нумератор для любого класса коллекции
ICollection Определяет элементы, которые должны иметь все коллекции
IComparer Определяет метод Compare( ) , который выполняет сравнение объектов, хранимых в коллекции
IList Определяет коллекцию, к которой можно получить доступ посредством индексатора
IDictionary Определяет коллекцию (словарь), которая состоит из пар ключ/значение
IDictionaryEnumerator Определяет нумератор для коллекции, которая реализует интерфейс IDictionary
IHashCodeProvider Определяет хеш-функцию

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

Интерфейс ICollection наследует интерфейс IEnumerable. В ICollection объявлены основные методы и свойства, без которых не может обойтись ни одна коллекция:

Элемент интерфейса Его тип описание
int Сount {get;} Свойство Определяет количество элементов коллекции в данный момент. Если Count равно нулю, то коллекция пуста.
void CopyTo (Array target, int startIdx) Метод Обеспечивает переход от коллекции к стандартному С#-массиву, копируя содержимое коллекции в массив, заданный параметром target, начиная с индекса, заданного параметром startIdx .

Интерфейс IList наследует интерфейс ICollection и определяет поведение коллекции, доступ к элементам которой разрешен посредством индекса с отсчетом от нуля. Помимо методов, определенных в интерфейсе ICollection, интерфейс IList определяет и собственные методы:

Элемент интерфейса Его тип Описание
int Add(object obj) Метод Добавляет объект obj в вызывающую коллекцию. Возвращает индекс, по которому этот объект сохранен
void Clear() Метод Удаляет все элементы из вызывающей коллекции
bool Contains(object obj) Метод Возвращает значение true, если вызывающая коллекция содержит объект, переданный в параметре obj, и значение false в противном случае
int IndexOf(object obj) Метод Возвращает индекс объекта obj, если он (объект) содержится в вызывающей коллекции. Если объект obj не обнаружен, метод возвращает -1
void Insert(int idx, object obj) Метод Вставляет в вызывающую коллекцию объект obj по индексу, заданному параметром idx. Элементы, находившиеся до этого по индексу idx и далее, смещаются вперед, чтобы освободить место для вставляемого объекта obj
void Remove(object obj) Метод Удаляет первое вхождение объекта obj из вызывающей коллекции. Элементы, находившиеся до этого за удаленным элементом, смещаются назад, чтобы ликвидировать образовавшуюся "брешь"
void RemoveAt(int idx) Метод Удаляет из вызывающей коллекции объект, расположенный по индексу, заданному параметром idx. Элементы, находившиеся до этого за удаленным элементом, смещаются, ликвидируя образовавшуюся "брешь"
bool IsFixedSize { get; } Свойство Принимает значение true, если коллекция имеет фиксированный размер. Это означает, что в такую коллекцию нельзя вставлять элементы и удалять их из нее.
bool IsReadOnly { get; } Свойство Принимает значение true, если коллекция предназначена только для чтения.
object this[int idx] { get; set; } Индексатор Используется для считывания или записи значения элемента с индексом idx. Нельзя применить для добавления в коллекцию нового элемента.

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

Элемент интерфейса Его тип Описание
void Add (object к, object v) метод Добавляет в вызывающую коллекцию пару ключ/значение, заданную параметрами к и v. Ключ к не должен быть нулевым. При попытке задать нулевой ключ генерируют исключение типа NotSupportedException Если окажется, что ключ к уже хранится в коллекции, генерируется исключение типа ArgumentException
void Clear () метод Удаляет все пары ключ/значение из вызывающей коллекции
bool Contains (object к) метод Возвращает значение true , если вызывающая коллекция содержит объект к в качестве ключа. В противном случае возвращает значение false
IDictionaryEnumerator GetEnumerator() метод Возвращает нумератор для вызывающей коллекции
void Remove (object к) метод Удаляет элемент, ключ которого равен значению к
bool isFixedSize { get } свойство Равно значению true , если коллекция имеет фиксированный размер
bool isReadOnly { get } свойство Равно значению true, если коллекция предназначена только для чтения
ICollection Keys { get } свойство Получает коллекцию ключей
icollection Values { get} свойство Получает коллекцию значений
object this[object key] { get; set; } индексатор Этот индексатор можно использовать для получения или установки значения элемента, а также для добавления в коллекцию нового элемента. "Индекс" в данном случае является ключом элемента.

Интерфейс IDictionaryEnumerator является производным от интерфейса IEnumerator и распространяет свои функциональные возможности нумератора на область словарей.

В интерфейсе IComparer определен метод Compare (), который позволяет сравнивать два объекта:

int Compare(object vl, object v2)

Метод Compare () возвращает положительное число, если значение vl больше значения v2, отрицательное, если vl меньше v2, и нуль, если сравниваемые значения равны. Этот интерфейс можно использовать для задания способа сортировки элементов коллекции.

Интерфейс IHashCodeProvider должен быть реализован коллекцией, если программисту необходимо определить собственную версию метода GetHashCode ().



<== предыдущая лекция | следующая лекция ==>
Использование защищенного доступа | Тема 7. Стандартные компоненты Delphi для ввода и редактирования данных


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.006 сек.