КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Другие элементы меню 23 страница
Person pers2 = new Person("Павлов", 35, 2100); OneLinkList< string, Person> list2 = new OneLinkList< string, Person>(); list2.add("Савл", pers1); list2.add("Павел", pers2); if (list2.findstart("Павел")) Console.WriteLine("Павел - найдено!"); else Console.WriteLine("Павел - не найдено!"); if (list2.findstart("Савл")) Console.WriteLine("Савл - найдено!"); else Console.WriteLine("Савл - не найдено!"); if (list2.findstart("Иоанн")) Console.WriteLine("Иоанн - найдено!"); else Console.WriteLine("Иоанн - не найдено!"); Person pers3 = new Person("Иванов", 33, 3000); list2.add("Иоанн", pers3); list2.start(); Person pers = list2.Item(); pers.PrintPerson(); list2.findstart("Иоанн"); pers = list2.Item(); pers.PrintPerson(); } Обратите внимание на строки, где создаются два списка: OneLinkList< int, string > list1 = new OneLinkList< int, string >(); OneLinkList< string, Person> list2 = new OneLinkList< string, Person>(); У списка list1 ключи имеют тип int, у списка list2 – string. Заметьте, оба фактических типа согласно обязательствам реализуют интерфейс IComparable. У первого списка тип элементов – string, у второго – Person. Все работает прекрасно. Вот результаты вычислений по этой процедуре: <Рис. 22.5. Поиск в списке с ограниченной универсальностью>
Как справиться с арифметикой Представьте себе, что мы хотим иметь специализированный вариант нашего списка, элементы которого допускали бы операцию сложения, и одно из полей которого сохраняло бы сумму всех элементов, добавленных в список. Как задать соответствующее ограничение на класс? Как уже говорилось, наличие ограничения операции, где можно было бы указать, что над элементами определена операция +, решало бы проблему. Но такого типа ограничений нет. Хуже того, нет и интерфейса INumeric, аналогичного IComparable, определяющего метод сложения Add. Так что нам не может помочь и ограничение наследования. Вот один из возможных выходов, предлагаемых в такой ситуации. Стратегия следующая: определим абстрактный универсальный класс Calc с методами, выполняющими вычисления. Затем создадим конкретизированных потомков этого класса. В классе, задающем список с суммированием, введем поле класса Calc. При создании экземпляров класса будем передавать фактические типы ключа и элементов, а также соответствующий калькулятор, но уже не как тип, а как аргумент конструктора класса. Этот калькулятор, согласованный с типом элементов, и будет выполнять нужные вычисления. Давайте приступим к реализации этой стратегии. Начнем с определения класса Calc: public abstract class Calc<T> { public abstract T Add(T a, T b); public abstract T Sub(T a, T b); public abstract T Mult(T a, T b); public abstract T Div(T a, T b); } Наш абстрактный универсальный класс определяет четыре арифметические операции. Давайте построим трех его конкретизированных потомков: public class IntCalc: Calc< int > { public override int Add(int a, int b) { return (a + b); } public override int Sub(int a, int b) { return (a - b); } public override int Mult(int a, int b) { return (a * b); } public override int Div(int a, int b) { return (a / b); } } public class DoubleCalc: Calc< double > { public override double Add(double a, double b) { return (a + b); } public override double Sub(double a, double b) { return (a - b); } public override double Mult(double a, double b) { return (a * b); } public override double Div(double a, double b) { return (a / b); } } public class StringCalc: Calc< string > { public override string Add(string a, string b) { return (a + b); } public override string Sub(string a, string b) { return (a); } public override string Mult(string a, string b) { return (a); } public override string Div(string a, string b) { return (a); } } Здесь определяются три разных калькулятора – один над целочисленными данными, другой – над данными с плавающей точкой, третий – над строковыми данными. В последнем случае определена, по сути, только операция сложения строк (конкатенации). Теперь нам нужно ввести изменения в ранее созданный класс OneLinkList. Обратите внимание на важный технологический принцип работы с объектными системами. Пусть уже есть нормально работающий класс с нормально работающими клиентами класса. Не следует изменять этот класс. Класс закрыт для изменений. Используйте наследование и открывайте класс потомок, в который и вносите изменения, учитывающие добавляемую специфику класса. Принцип «Закрыт – Открыт» является одним из важнейших принципов построения программных систем в объектном стиле. В полном соответствии с этим принципом построим класс SumList – потомок класса OneLinkList. То, что родительский класс является универсальным, ничуть не мешает строить потомка класса, сохраняющего универсальный характер родителя. public class SumList<K, T>: OneLinkList<K, T> where K: IComparable<K> { Calc<T> calc; T sum; public SumList(Calc<T> calc) { this. calc = calc; sum = default (T); }
public new void add(K key, T item) { Node<K, T> newnode = new Node<K, T>(); if (first == null) { first = newnode; cursor = newnode; newnode.key = key; newnode.item = item; sum = calc.Add(sum, item); } { newnode.next = cursor.next; cursor.next = newnode; newnode.key = key; newnode.item = item; sum = calc.Add(sum, item); } } public T Sum() { return (sum); } }//SumList У класса добавилось поле sum, задающее сумму хранимых элементов, и поле calc – калькулятор, выполняющий вычисления. Метод add, объявленный в классе с модификатором new, скрывает родительский метод add, задавая собственную реализацию этого метода. Родительский метод можно было бы определить как виртуальный, переопределив его у потомка, но я не стал трогать код родительского класса. К классу добавился еще один запрос, возвращающий значение поля sum. Некоторые изменения в уже существующем проекте пришлось таки сделать, изменив статус доступа у полей. А все потому, что в целях экономии текста кода я не стал закрывать поля и вводить, как положено, открытые процедуры-свойства для закрытых полей. Проведем теперь эксперименты с новыми вариантами списков, допускающих суммирование элементов: public void TestSum() { SumList< string, int > list1 = new SumList< string, int >(new IntCalc()); list1.add("Петр", 33); list1.add("Павел", 44); Console.WriteLine("sum= {0}", list1.Sum()); SumList< string, double > list2 = new SumList< string, double >(new DoubleCalc()); list2.add("Петр", 33.33); list2.add("Павел", 44.44); Console.WriteLine("sum= {0}", list2.Sum()); SumList< string, string > list3 = new SumList< string, string >(new StringCalc()); list3.add("Мама", " Мама мыла "); list3.add("Маша", "Машу мылом!"); Console.WriteLine("sum= {0}", list3.Sum()); } Обратите внимание на создание списков: SumList< string, int > list1 = new SumList< string, int >(new IntCalc()); SumList< string, double > list2 = new SumList< string, double >(new DoubleCalc()); SumList< string, string > list3 = new SumList< string, string >(new StringCalc()); Как видите, конструктору объекта передается калькулятор, согласованный с типами данных, хранимых в списке. Приведу результаты вычислений, полученными при работе с этими списками: <Рис. 22.6. Списки с суммированием> Родовое порождение класса. Предложение using До сих пор рассматривалась ситуация родового порождения экземпляров универсального класса. Фактические типы задавались в момент создания экземпляра. Это наглядно показывает преимущества применяемой технологии, поскольку очевидно, что не создается дублирующий код для каждого класса, порожденного универсальным классом. И все-таки остается естественный вопрос, а можно ли породить класс из универсального класса путем подстановки фактических параметров, а потом спокойно использовать этот класс обычным образом? Такая вещь возможна. Это можно сделать не совсем обычным путем – не в программном коде, а в предложении using, назначение которого и состоит в выполнении подобных подстановок. Давайте вернемся к универсальному классу OneLinkStack<T>, введенному в начале этой лекции и породим на его основе вполне конкретный класс IntStack, заменив формальный параметр T фактическим – int. Для этого достаточно задать следующее предложение using: using IntStack = Generic.OneLinkStack< int >; Вот тест, в котором создаются несколько объектов этого класса: public void TestIntStack() { IntStack stack1 = new IntStack(); IntStack stack2 = new IntStack(); IntStack stack3 = new IntStack();
stack1.put(11); stack1.put(22); int x1 = stack1.item(), x2 = stack1.item(); if ((x1 == x2) && (x1 == 22)) Console.WriteLine("OK!"); stack1.remove(); x2 = stack1.item(); if ((x1!= x2) && (x2 == 11)) Console.WriteLine("OK!"); stack1.remove(); x2 = (stack1.empty())? 77: stack1.item(); if ((x1!= x2) && (x2 == 77)) Console.WriteLine("OK!");
stack2.put(55); stack2.put(66); stack2.remove(); int s = stack2.item(); if (!stack2.empty()) Console.WriteLine(s);
stack3.put(333); stack3.put((int)Math.Sqrt(Math.PI)); int res = stack3.item(); stack3.remove(); res += stack3.item(); Console.WriteLine("res= {0}", res); } Все работает заданным образом, можете поверить. Универсальность и специальные случаи классов Универсальность – это механизм, воздействующий на все элементы языка. Поэтому он применим ко всем частным случаям классов C#. Универсальные структуры Также как и обычный класс, структура может иметь родовые параметры. Синтаксис объявления, ограниченная универсальность, другие детали универсальности естественным образом распространяются на структуры. Вот типичный пример: public struct Point<T> { T x, y;//координаты точки, тип которых задан параметром // другие свойства и методы структуры } Универсальные интерфейсы Интерфейсы чаще всего следует делать универсальными, предоставляя большую гибкость для позднейших этапов создания системы. Возможно, вы заметили применение в наших примерах универсальных интерфейсов библиотеки FCL – IComparable<T> и других. Введение универсальности в первую очередь сказалось на библиотеке FCL – внутренних классов, определяющих поведение системы. В частности, для большинства интерфейсов, появились их универсальные двойники с параметрами. Если бы в наших примерах мы использовали бы не универсальный интерфейс, а обычный, то потеряли бы в эффективности, поскольку сравнение объектов потребовало бы создание временных объектов типа object, выполнения операций boxing и unboxing. Универсальные делегаты Делегаты также могут иметь родовые параметры. Чаще встречается ситуация, когда делегат объявляется в универсальном классе и использует в своем объявлении параметры универсального класса. Давайте рассмотрим ситуацию с делегатами более подробно. Вот объявление универсального класса, не очень удачно названного Delegate, в котором объявляется функциональный тип – delegate: class Delegate<T> { public delegate T Del(T a, T b); } Как видите, тип аргументов и возвращаемого значения в сигнатуре функционального типа определяется классом Delegate. Добавим в класс функцию высшего порядка FunAr, одним из аргументов которой будет функция типа Del, заданного делегатом. Эта функция будет применяться к элементам массива, передаваемого также функции FunAr. Приведу описание: public T FunAr(T[] arr, T a0, Del f) { T temp = a0; for (int i =0; i<arr.Length; i++) { temp = f(temp, arr[i]); } return (temp); } Эта универсальная функция с успехом может применяться для вычисления сумм, произведения, минимума и других подобных характеристик массива. Рассмотрим теперь клиентский класс Testing, в котором определен набор функций: public int max2(int a, int b) { return (a > b)? a: b; } public double min2(double a, double b) { return (a < b)? a: b; } public string sum2(string a, string b) { return a + b; } public float prod2(float a, float b) { return a * b; } Хотя все функции имеют разные типы, все они соответствуют определению класса Del – имеют два аргумента одного типа и возвращают результат того же типа. Посмотрим, как они применяются в тестирующем методе класса Testing: public void TestFun() { int [] ar1 = { 3, 5, 7, 9 }; double [] ar2 = { 3.5, 5.7, 7.9 }; string [] ar3 = { "Мама ", "мыла ", "Машу ", "мылом." }; float [] ar4 = { 5f, 7f, 9f, 11f }; Delegate< int > d1 = new Delegate< int >(); Delegate< int >.Del del1; del1= this. max2; int max = d1.FunAr(ar1, ar1[0], del1); Console.WriteLine("max= {0}", max);
Delegate< double > d2 = new Delegate< double >(); Delegate< double >.Del del2; del2 = this. min2; double min = d2.FunAr(ar2, ar2[0], del2); Console.WriteLine("min= {0}", min);
Delegate< string > d3 = new Delegate< string >(); Delegate< string >.Del del3; del3 = this. sum2; string sum = d3.FunAr(ar3, "", del3); Console.WriteLine("concat= {0}", sum);
Delegate< float > d4 = new Delegate< float >(); Delegate< float >.Del del4; del4 = this. prod2; float prod = d4.FunAr(ar4, 1f, del4); Console.WriteLine("prod= {0}", prod); } Обратите внимание на объявление экземпляра делегата: Delegate< int >.Del del1; В момент объявления задается фактический тип, и сигнатура экземпляра становится конкретизированной. Теперь экземпляр можно создать и связать с конкретной функцией. В C# 2.0 это делается проще и естественнее, чем ранее – непосредственным присваиванием: del1= this.max2; При выполнении этого присваивания выполняются довольно сложные действия – проверяется соответствие сигнатуры функции в правой части и экземпляра делегата, в случае успеха создается новый экземпляр делегата, который и связывается с функцией. Покажем, что и сам функциональный тип – делегат можно объявлять с родовыми параметрами. Вот пример такого объявления: public delegate T FunTwoArg<T>(T a, T b); Добавим в наш тестовый пример код, демонстрирующий работу с этим делегатом: FunTwoArg< int > mydel; mydel = max2; max = mydel(17, 21); Console.WriteLine("max= {0}", max); Вот как выглядят результаты работы тестового примера: <Рис. 22.7. Результаты работы с универсальными делегатами> Универсальные делегаты с успехом применяются при определении событий. В частности, класс EventHandler, применяемый для всех событий, не имеющих собственных аргументов, теперь дополнен универсальным аналогом, определенным следующим образом: public void delegate EventHandler<T> (object sender, T args) where T:EventArgs Этот делегат может применяться и для событий с собственными аргументами, поскольку вместо параметра T может быть подставлен конкретный тип – потомок класса EventArgs, дополненный нужными аргументами. Framework.Net и универсальность Универсальность принадлежит к основным механизмам языка. Ее введение в язык C# не могло не сказаться на всех основных свойствах языка. Как уже говорилось, классы и все частные случаи стали обладать этим свойством. Введение универсальности не должно было ухудшить уже достигнутые свойства языка – статический контроль типов, динамическое связывание и полиморфизм. Не должна была пострадать и эффективность выполнения программ, использующих универсальные классы. Решение этих задач потребовало введения универсальности не только в язык C#, но и поддержки на уровне каркаса Framework.Net и языка IL, включающем теперь параметризованные типы. Универсальный класс C# не является шаблоном, на основе которого строится конкретизированный класс, компилируемый далее в класс (тип) IL. Компилятору языка C# нет необходимости создавать классы для каждой конкретизации типов универсального класса. Вместо этого происходит компиляция универсального класса C# в параметризованный тип IL. Когда же CLR занимается исполнением управляемого кода, то вся необходимая информация о конкретных типах извлекается из метаданных, сопровождающих объекты. При этом дублирования кода не происходит и на уровне JIT-компиляторов, которые, однажды сгенерировав код для конкретного типа, сохраняют ссылку на этот участок кода и передают ее, когда такой код понадобится вторично. Это справедливо, как для ссылочных, так и значимых типов. Естественно, что универсальность потребовала введения в библиотеку FCL соответствующих классов, интерфейсов, делегатов и методов классов, обладающих этим свойством. Так, например, в класс System.Array добавлен ряд универсальных статических методов. Вот один из них: public static int BinarySearch<T>(T[] array, T value); В таблице 22.1 показаны некоторые универсальные классы и интерфейсы библиотеки FCL 2.0 из пространства имен System.Collections.Generic и их аналоги из пространства System.Collections. Таблица 22-1. Соответствие между универсальными классами и их обычными двойниками
Сериализация и универсализация также согласуются друг с другом, так что можно иметь универсальный класс, для которог задан атрибут сериализации. Вариант 1 61. Универсальный класс – это: q шаблон, используемый для построения конкретных классов; q класс, способный решать любые задачи пользователя; q класс, методы которого могут иметь родовые параметры; q класс с родовыми параметрами; q класс, функциональные возможности которого превосходят возможности его потомков. 62. Отметьте истинные высказывания: q наследник универсального класса всегда универсален; q для универсального класса не может указываться атрибут сериализации; q родовые параметры универсального класса могут задаваться как на уровне класса, так и на уровне методов; q класс без родовых параметров может быть наследником универсального интерфейса; q делегаты могут иметь родовые параметры. 63. Какие фрагменты кода корректны? q class A<B, C> { B b; C c;} q class A1<A1, B> { A1 a1; B b;} q class A2<B>: IC<C> { } q class A3<B> where B: A<int, int> { } Вариант 2 65. Для ограниченной универсальности справедливы следующие утверждения: q существуют три вида ограничений универсальности; q ограничение универсальности накладывает ограничения на все родовые параметры; q неограниченную универсальность ограничивает статический контроль типов; q введение ограничений универсальности на родовой параметр T сужает возможности работы с объектами типа T; q в одном предложении where можно задать ограничения на несколько родовых параметров. 66. Отметьте истинные высказывания: q наследник универсального класса может быть универсальным классом; q структуры не могут иметь родовых параметров; q в библиотеке FCL существуют универсальные классы и универсальные интерфейсы; q универсальность реализована, начиная с версии Framework.Net 2.0; q платой за универсальность является раздувание программного кода системы. 67. В каких фрагментах кода возникнут ошибки (предупреждения) на этапе компиляции: q public class B<T> { } public class S: B<int> { } q public class Test1:IComparable<T> {public T x, y;} q public class B1<T> {public T M<T>(T x) { return x; } } q public class B2<T> {public T1 M<T1>(T1 x) { return x; } } Вариант 3 62. Какие утверждения справедливы при описании взаимосвязи наследования и универсальности? q основным способом ограничения универсальности является ограничение наследования; q ограничение наследования, накладываемое на родовой параметр T, в качестве базового класса не может использовать универсальный класс; q из абстрактного универсального класса нельзя создать класс, в котором конкретизированы типы, но не задана реализация методов; q из абстрактного универсального класса можно создать класс, в котором конкретизирована реализация методов, но не конкретизированы типы; q при объявлении потомка универсального класса можно задать конкретизацию типов родителя, так что потомок не будет универсальным классом. 63. Отметьте корректные высказывания: q универсальность и наследование позволяют существенно сократить объем кода программной системы; q три типа ограничений универсальности полностью удовлетворяют всем потребностям практики; q конкретизацию типов можно выполнять в момент порождения экземпляров универсального класса; q прежде чем порождать экземпляры на основе универсального класса, необходимо предварительно создать класс, в котором все родовые параметры будут конкретизированы; q универсальный класс транслируется в параметризованный тип IL. 64. В каких строках кода возникнут ошибки (предупреждения) на этапе трансляции: q abstract public class A5<T> { abstract public void M(T x); } q public class SA5<T>: A5<T> { public override void M(T x) { T y = x; } } q public class B5 { A5<int> obj1 = new A5<int>(); } q public class B6 { SA5<int> obj1 = new SA5<int>(); } q public class B7 { SA5<T> obj1 = new SA5<T>(); }
Лекция 23. Отладка и обработка исключительных ситуаций Корректность и устойчивость. Cпецификация системы. Корректность и устойчивость программных систем. Исключительные ситуации. Обработка исключительных ситуаций. Жизненный цикл программной системы. Три закона программотехники. Отладка. Создание надежного кода. Искусство отладки. Отладка и инструментальная среда Visual Studio.Net. Ключевые слова: корректность; устойчивость; отладка; жизненный цикл; программотехника; закон для разработчика; закон для пользователя; закон чечако; отладка; надежный код;повторное использование; статический контроль типов; динамическое связывание; полиморфизм; автоматическая сборка мусора; спецификации; методы повторного использования компонентов; надежный код; тестеры; корректность программы; две группы средств; программные средства; отладочная печать; механизм условной компиляции; константы условной компиляции; методы печати данных; методы доказательства правильности программ; метод Флойда; обработка исключений;исключительная ситуация; блоки-обработчики исключительных ситуаций; выбрасывание исключений; тип исключения; классы исключений «захват» исключения; универсальный обработчик; специализированные обработчики; схема без возобновления; Проектирование по Контракту; схема Бертрана; класс Exception. Корректность и устойчивость программных систем Корректность и устойчивость – два основных качества программной системы, без которых все остальные ее достоинства не имеют особого смысла. Понятие корректности программной системы имеет смысл только тогда, когда задана спецификация этой системы. В зависимости от того, как формализуется спецификация, уточняется понятие корректности. В лекции 9 введено строгое понятие корректности метода по отношению к его спецификациям, заданным в виде предусловия и постусловия метода. Корректность – это способность программной системы работать в строгом соответствии со своей спецификацией. Отладка – процесс, направленный на достижение корректности. Во время работы системы могут возникать ситуации, выходящие за пределы, предусмотренные спецификацией. Такие ситуации называются исключительными. Устойчивость – это способность программной системы должным образом реагировать на исключительные ситуации. Обработка исключительных ситуаций – процесс, направленный на достижение устойчивости.
Дата добавления: 2014-12-25; Просмотров: 525; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |