Контент-платформа Pandia:     2 872 000 материалов , 128 197 пользователей.     Регистрация


Парадоксы и «дыры» языка C#

 просмотров

Парадоксы и «дыры» языка C#

В языке C# нет академической строгости, отточенности конструкций, характерных для таких языков как Паскаль или Эйфель. Поэтому в программах C# можно зачастую встретиться с парадоксами. Результаты выполнения программы для математика явно покажутся некорректными, хотя они полностью соответствуют семантике языка.

Серия таких парадоксов была продемонстрирована на конференции разработчиков SECR 2008 в докладе Гайдара Магданурова «Подводные камни C#». О проблемах языка C# говорилось и в моем докладе «ООП, C# и некоторые проблемы» на конференции «Преподавание ИТ в России» в Нижнем Новгороде, в мае 2008 г.

Парадоксы

Большинство парадоксов связано с достаточной сложной семантикой вычисления выражений языка, где присутствуют большое число операций разного приоритета, перегрузка операций и неявные преобразования. Вызывает вопросы и порядок вычисления выражения. Поясню это на примере вычисления выражения x + ++x. Наивысший приоритет в этом выражении имеет префиксная операция ++x. Казалось бы, с нее и должно начинаться вычисление выражения. Но это не так. Вначале будет вычислен первый операнд операции сложения – x. Только затем будет вычислено значение ++x, а затем произойдет сложение операндов.

В дополнение к примерам, приведенным в докладе Гайдара Магданурова, приведу несколько парадоксов.

Пример 1:

byte b1 = 1, b2 = 2, b3 = b1 + b2;

if (b3 > b1)

Console. WriteLine("OK!");

else

Console. WriteLine("wow!");

Что будет напечатано – OK или wow? Ни то, ни другое! Программа выдаст ошибку еще на этапе компиляции. Это парадоксальный результат! Проведем «Тест математика», показав эту программу не программисту, а классическому математику. Можно утверждать со стопроцентной гарантией, что математик скажет, что результатом должен быть «OK». То, что C# такой тест не проходит, говорит о том, что C# это не классика, а рабочая лошадка программиста. Конечно, программист C# объяснит, что + - это перегруженная операция, определенная не для всех типов операндов, поэтому неявно операнды будут приведены к типу int, результат сложения будет иметь тип int, который автоматически не приводится к типу byte. Согласитесь, довольно сложная семантика для простой операции сложения.

Давайте исправим код, приведя его в соответствие с правилами языка C#.

byte b1 = 1, b2 = 2, b3 = (byte)(b1 + b2);

if (b3 > b1)

Console. WriteLine("OK!");

else

Console. WriteLine("wow!");

Теперь все соответствует ожиданиям.

Слегка модифицируем код.

byte b1 = 100, b2 = 200, b3 = (byte)(b1 + b2);

if (b3 > b1)

Console. WriteLine("OK!");

else

Console. WriteLine("wow!");

И снова парадокс: все работает! Но результат неверен!! На печать буден выдано «wow». Математик был бы поражен этим результатом. Программист C# ему объяснит, что результат выходит за допустимые типом byte пределы и потому старший разряд потерян и никаких сообщений об ошибке не выдается, поскольку ответственность за явное преобразование несет программист, создавший этот код.

И в следующем примере «Тест математика» не проходит.

double a = 1.5;

if (a + 7/8 > 2)

Console. WriteLine("OK!");

else

Console. WriteLine("wow!");

На печать будет выдано «wow», а изумленному математику программист C# объяснит, что операция деления перегружена и для целых операндов она выдает результат деления нацело, то есть ноль в нашем примере, и нужно было поставить точку в записи одного из операндов для получения правильного результата, например, так 7.0/8.

В следующем примере нарушается привычная для математика коммутативность сложения.

int x = 1, y = 0;

y = x++ + x + ++x;

Console. WriteLine("x = {0}, y = {1}", x, y);

x = 1;

y = x + x++ + ++x;

Console. WriteLine("x = {0}, y = {1}", x, y);

Два выражения, эквивалентные с точки зрения математика, дают разные результаты из-за побочных эффектов. Этот пример неоднозначен и для C# программиста. Приоритет операций и порядок вычисления выражений в C# плохо согласуются друг с другом. Выше я коротко рассказал, как вычисляются выражения в языке C#. Для полного понимания полезно проанализировать ассемблерный код на IL, построенный компилятором C#.

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

int x = 1, y = 2, z = ((x | y + ++x) > 5) ? x | y : ++x + 2;

Console. WriteLine("x ={0}, y = {1}, z = {2}", x, y, z);

У программирования и классической математики свои законы, что убедительно показывает следующий пример:

int x = 1, y = 1;

long k = 0;

for (int i = 1; i > 0; i++)

{

x += y; y = - y; k++;

}

Console. WriteLine("x = {0}, y = {1}, k = {2}",x, y, k);

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

Рассмотрим теперь несколько более серьезных проблем языка C#.

Проблемы

Проблема 1: Конфликт наименований

Описание проблемы:

При присоединении новой DLL к успешно работающему проекту может возникать конфликт имен, в результате которого нарушится корректность работы проекта. К сожалению, в момент присоединения DLL никаких сообщений о конфликте имен выдано не будет.

Эта проблема была описана в статье Сергея Свердлова, а позже в его книге [1].

Рассмотрим пример, демонстрирующий проблему.

Построим Solution с именем Problem1, в котором разместим 3 проекта:

DLL с именем A,

DLL с именем B,

Консольный проект с именем NamingConflict

Вот текст DLL A:

using System;

using System. Collections. Generic;

using System. Text;

namespace A

{

public class B

{

public class C

{

public static string D = "the best!";

}

}

}

Вот текст DLL B:

using System;

using System. Collections. Generic;

using System. Text;

namespace B

{

public class C

{

public static double D = 3.141593;

}

}

А вот текст проекта NamingConflict:

using System;

using System. Text;

using A;

namespace NamingConflict

{

class Program

{

static void Main(string[] args)

{

Console. Write("Valuation of the language C# is ");

Console. WriteLine(B. C.D);

}

}

}

Присоединим к консольному проекту NamingConflict проект - DLL A - и запустим его на выполнение.

Получим ожидаемый результат:

Valuation of the language C# is the best!

Присоединим теперь DLL B к проекту NamingConflict и снова запустим проект на выполнение. Результат работы может показаться неожиданным:

Valuation of the language C# is 3.141593

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

Возможные решения проблемы

Решить проблему можно по-разному, например:

·  Применить жесткие правила именования, требуя обязательного указания имени DLL (пространства имен);

·  При присоединении новой DLL проверять возможность конфликта имен, выдавая соответствующее сообщение.

Проблема 2: Конфликт обработчиков событий

Описание проблемы:

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

Эта проблема была описана в моей книге [2].

Рассмотрим пример, демонстрирующий проблему.

Построим консольный проект ExpertOrHacker. Добавим в проект класс Developer с событием Choose.

using System;

using System. Collections. Generic;

using System. Text;

namespace ExpertOrHacker

{

/// <summary>

/// class with event Choose

/// The event is raising when a developer have to choose a language

/// </summary>

class Developer

{

public event EventHandler<ChooseEventArgs> choose;

//fields of class

private string language_name = "";

private string language_reference = "";

public string Language_Name

{ get { return language_name; } }

public string Language_Reference

{

get { return language_reference; }

set { language_reference = value; }

}

public void ChooseLanguage()

{

language_name = "C#";

OnChoose();

}

protected void OnChoose()

{

ChooseEventArgs args = new ChooseEventArgs(language_name);

if (choose!= null)

{

choose(this, args);

language_reference = args. Reference;

foreach (EventHandler<ChooseEventArgs> current in choose. GetInvocationList())

{

Console. WriteLine(current. Target);

}

}

}

}

}

Добавим в проект класс ChooseEventArgs, описывающий входные и выходные аргументы события Choose.

using System;

using System. Collections. Generic;

using System. Text;

namespace ExpertOrHacker

{

/// <summary>

/// Arguments of event Choose

/// The event is raising when a developer have to choose a language

/// input parameter name is a name of language

/// output parameter reference is expert opinion

/// </summary>

class ChooseEventArgs:EventArgs

{

private readonly string name;

private string reference;

//constructor

public ChooseEventArgs(string name)

{

this. name = name; this. reference = "";

}

public string Name

{

get { return name; }

}

public string Reference

{

set { reference = value; }

get { return reference; }

}

}

}

Добавим в проект два класса, объекты которых могут обрабатывать событие Choose.

using System;

using System. Collections. Generic;

using System. Text;

namespace ExpertOrHacker

{

/// <summary>

/// receiver of message about Choose event

/// </summary>

class Expert

{

Developer dev;

public Expert(Developer d)

{

dev = d;

OnConnect();

}

protected void OnConnect()

{

dev. choose += new EventHandler<ChooseEventArgs>(ExpertChoose);

}

protected void OffConnect()

{

dev. choose -= new EventHandler<ChooseEventArgs>(ExpertChoose);

}

protected void ExpertChoose(object sender, ChooseEventArgs e)

{

if (e. Name == "C#")

e. Reference = "the best!";

else

e. Reference = " good but Eiffel is the best!";

}

}

/// <summary>

/// receiver of message about Choose event

/// </summary>

class Hacker

{

Developer dev;

public Hacker(Developer d)

{

dev = d;

OnConnect();

}

protected void OnConnect()

{

dev. choose += new EventHandler<ChooseEventArgs>(HackerChoose);

}

protected void OffConnect()

{

dev. choose -= new EventHandler<ChooseEventArgs>(HackerChoose);

}

protected void HackerChoose(object sender, ChooseEventArgs e)

{

if (e. Name == "C#")

e. Reference = "the worst!";

else

e. Reference = "good but Eiffel is the best!";

}

}

}

Приведу теперь текст процедуры Main консольного проекта.

using System;

using System. Collections. Generic;

using System. Text;

namespace ExpertOrHacker

{

class Program

{

static void Main(string[] args)

{

Developer developer = new Developer();

// Expert expert = new Expert(developer);

Hacker hacker = new Hacker(developer);

Expert expert = new Expert(developer);

developer. ChooseLanguage();

Console. WriteLine("The language {0} is {1}",

developer. Language_Name, developer. Language_Reference);

}

}

}

В процедуре Main консольного проекта создается объект класса Developer и объекты классов Expert и Hacker, которые присоединяются к списку вызовов события Choose, после чего вызывается метод ChooseLanguage объекта developer, зажигающий событие Choose.

Результатом работы будет утверждение:

The language C# is the best!

Поменяем теперь в процедуре Main порядок присоединения к списку вызовов объектов hacker и expert. Тперь в качества результата будет напечатано:

The language C# is the worst!

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

Возможные решения проблемы

Решить проблему можно по-разному, например:

·  Запретить выходные аргументы у событий;

·  Положиться на разработчиков и рекомендовать им следовать строгой дисциплине при разработке классов, создающих и обрабатывающих события;

·  Дополнить класс MulticastDelegate соответствующим полем Result, наряду с полем Target. Позволить обработчику события в этом поле формировать значение выходного аргумента. При таком подходе конфликты станут невозможными, а в методе, зажигающем событие, появится возможность анализировать значения выходных аргументов каждого из обработчиков события. Для этого достаточно будет вызывать метод GetInvocationList.

Проблема 3 Ненадежность схемы обработки исключительных ситуаций

Описание проблемы.

Возникновение исключительной ситуации при работе метода означает, что метод не может выполнить свой контракт, - корректно выполнить возложенную на него работу. Схема try-catch-finally блоков языка C#, позволяющая обрабатывать исключительные ситуации, не гарантирует корректной работы метода и позволяет продолжить работу в условиях, когда метод не выполнил свой контракт, что может приводить к печальным последствиям.

Эта проблема подробно обсуждается в книге [3].

Рассмотрим пример, демонстрирующий проблему.

static void Problem3()

{

int test = 0;

try

{

test = GetTestResult();

if (test > 5)

throw new Exception("Отрицательный тест!");

}

catch (Exception)

{

Console. WriteLine("Операция опасна!");

}

Operation();

if(test <= 5)

Console. WriteLine("Больной спасен!");

else

Console. WriteLine("Больного спасти не удалось!");

}

static int GetTestResult()

{

return rnd. Next(10);

}

static void Operation()

{

Console. WriteLine("Операция проведена!");

}

Что делает обработчик исключительной ситуации? – выдает сообщение о недопустимости операции, но не приостанавливает выполнение самой операции. Операция выполняется и пациент погибает, когда результаты анализа отрицательны и противопоказаны проведению операции.

Возможное решение проблемы

Корректная схема обработки исключительных ситуаций описана Бертраном Мейером и реализована в спроектированном им языке Эйфель [3]. У обработчика исключительной ситуации в такой схеме есть две возможности – вернуться в модуль М, вызвавший исключительную ситуацию, либо выбросить новое исключение. В первом случае модуль может после корректировки попытаться выполнить свой контракт. Во втором случае корректность работы может попытаться обеспечить модуль, вызывающий модуль М. Точно гарантируется, что не будет продолжена работа, когда контракт не выполняется.

Литература

1.  «Языки программирования и методы трансляции». Изд. «Питер», 2007 г.

2.  «Основы программирования на C#». Изд. «Бином», «Интернет-Университет ИТ», 2006 г.

3.  Бертран Мейер «Объектно-ориентированное конструирование программных систем». Изд. «Русская Редакция», «Интернет-Университет ИТ», 2006 г.



Мы в соцсетях:


Подпишитесь на рассылку:
Посмотрите по Вашей теме:

Парадоксы


Проекты по теме:

Основные порталы, построенные редакторами

Домашний очаг

ДомДачаСадоводствоДетиАктивность ребенкаИгрыКрасотаЖенщины(Беременность)СемьяХобби
Здоровье: • АнатомияБолезниВредные привычкиДиагностикаНародная медицинаПервая помощьПитаниеФармацевтика
История: СССРИстория РоссииРоссийская Империя
Окружающий мир: Животный мирДомашние животныеНасекомыеРастенияПриродаКатаклизмыКосмосКлиматСтихийные бедствия

Справочная информация

ДокументыЗаконыИзвещенияУтверждения документовДоговораЗапросы предложенийТехнические заданияПланы развитияДокументоведениеАналитикаМероприятияКонкурсыИтогиАдминистрации городовПриказыКонтрактыВыполнение работПротоколы рассмотрения заявокАукционыПроектыПротоколыБюджетные организации
МуниципалитетыРайоныОбразованияПрограммы
Отчеты: • по упоминаниямДокументная базаЦенные бумаги
Положения: • Финансовые документы
Постановления: • Рубрикатор по темамФинансыгорода Российской Федерациирегионыпо точным датам
Регламенты
Термины: • Научная терминологияФинансоваяЭкономическая
Время: • Даты2015 год2016 год
Документы в финансовой сферев инвестиционнойФинансовые документы - программы

Техника

АвиацияАвтоВычислительная техникаОборудование(Электрооборудование)РадиоТехнологии(Аудио-видео)(Компьютеры)

Общество

БезопасностьГражданские права и свободыИскусство(Музыка)Культура(Этика)Мировые именаПолитика(Геополитика)(Идеологические конфликты)ВластьЗаговоры и переворотыГражданская позицияМиграцияРелигии и верования(Конфессии)ХристианствоМифологияРазвлеченияМасс МедиаСпорт (Боевые искусства)ТранспортТуризм
Войны и конфликты: АрмияВоенная техникаЗвания и награды

Образование и наука

Наука: Контрольные работыНаучно-технический прогрессПедагогикаРабочие программыФакультетыМетодические рекомендацииШколаПрофессиональное образованиеМотивация учащихся
Предметы: БиологияГеографияГеологияИсторияЛитератураЛитературные жанрыЛитературные героиМатематикаМедицинаМузыкаПравоЖилищное правоЗемельное правоУголовное правоКодексыПсихология (Логика) • Русский языкСоциологияФизикаФилологияФилософияХимияЮриспруденция

Мир

Регионы: АзияАмерикаАфрикаЕвропаПрибалтикаЕвропейская политикаОкеанияГорода мира
Россия: • МоскваКавказ
Регионы РоссииПрограммы регионовЭкономика

Бизнес и финансы

Бизнес: • БанкиБогатство и благосостояниеКоррупция(Преступность)МаркетингМенеджментИнвестицииЦенные бумаги: • УправлениеОткрытые акционерные обществаПроектыДокументыЦенные бумаги - контрольЦенные бумаги - оценкиОблигацииДолгиВалютаНедвижимость(Аренда)ПрофессииРаботаТорговляУслугиФинансыСтрахованиеБюджетФинансовые услугиКредитыКомпанииГосударственные предприятияЭкономикаМакроэкономикаМикроэкономикаНалогиАудит
Промышленность: • МеталлургияНефтьСельское хозяйствоЭнергетика
СтроительствоАрхитектураИнтерьерПолы и перекрытияПроцесс строительстваСтроительные материалыТеплоизоляцияЭкстерьерОрганизация и управление производством

Каталог авторов (частные аккаунты)

Авто

АвтосервисАвтозапчастиТовары для автоАвтотехцентрыАвтоаксессуарыавтозапчасти для иномарокКузовной ремонтАвторемонт и техобслуживаниеРемонт ходовой части автомобиляАвтохимиямаслатехцентрыРемонт бензиновых двигателейремонт автоэлектрикиремонт АКППШиномонтаж

Бизнес

Автоматизация бизнес-процессовИнтернет-магазиныСтроительствоТелефонная связьОптовые компании

Досуг

ДосугРазвлеченияТворчествоОбщественное питаниеРестораныБарыКафеКофейниНочные клубыЛитература

Технологии

Автоматизация производственных процессовИнтернетИнтернет-провайдерыСвязьИнформационные технологииIT-компанииWEB-студииПродвижение web-сайтовПродажа программного обеспеченияКоммутационное оборудованиеIP-телефония

Инфраструктура

ГородВластьАдминистрации районовСудыКоммунальные услугиПодростковые клубыОбщественные организацииГородские информационные сайты

Наука

ПедагогикаОбразованиеШколыОбучениеУчителя

Товары

Торговые компанииТоргово-сервисные компанииМобильные телефоныАксессуары к мобильным телефонамНавигационное оборудование

Услуги

Бытовые услугиТелекоммуникационные компанииДоставка готовых блюдОрганизация и проведение праздниковРемонт мобильных устройствАтелье швейныеХимчистки одеждыСервисные центрыФотоуслугиПраздничные агентства