Студопедия

КАТЕГОРИИ:


Архитектура-(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. Соответствие между универсальными классами и их обычными двойниками

Универсальный класс Обычный класс Универсальный интерфейс Обычный интерфейс
Comparer<T> Comparer ICollection<T> ICollection
Dictionary<K,T> HashTable IComparable<T> IComparable
LinkedList<T> ---- IDictionary<K,T> IDictionary
List<T> ArrayList IEnumerable<T> IEnumerable
Queue<T> Queue IEnumerator<T> IEnumerator
SortedDictionary<K,T> SortedList IList<T> IList
Stack<T> Stack    

Сериализация и универсализация также согласуются друг с другом, так что можно иметь универсальный класс, для которог задан атрибут сериализации.

Вариант 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; Просмотров: 486; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




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