Сообщений 27    Оценка 385 [+1/-0]         Оценить  
Система Orphus

Нововведения во второй публичной альфа версии VS 2005 (Whidbey) и .NET Framework

Автор: Владислав Чистяков (VladD2)
The RSDN Group

Источник: RSDN Magazine #2-2004
Опубликовано: 27.07.2004
Исправлено: 14.12.2005
Версия текста: 1.0
Введение
Новое название
IDE
Внешний вид
Улучшения в редакторе кода
Ассоциирование типов файлов с редакторами
Свойства проекта теперь открываются как немодальное окно
Изменения в Class View
Новый редактор ресурсов
Code Definition View
Object Test Bench
Find symbol
Улучшен Object Browser
Insert Expression
Debug
Окно Immediate
Точки останова
Хинты
Атрибуты, связанные с отладкой
Новое в CLR
Изменения в C#
Static-классы
Nullable-типы
Ограничения struct и class
Массивы фиксированного размера
Разный уровень доступа для accessor-ов свойств
extern alias
Доступ к алиасам пространств имен
Заключение

Введение

Эта статья является логическим развитием статей из 6-го номера нашего журнала за 2003 год. В данной статье я остановлюсь в основном на возможностях, предназначенных в первую очередь для C#-программистов. Не секрет, что Microsoft позиционирует C# как Code based RAD Tools, т.е. как средство быстрой разработки с упором на ручное кодирование. Видимо поэтому улучшения, связанные с IDE и языком C#, в основном направлены именно на упрощение и ускорение именно ручного кодирования.

Новое название

Практически сразу после выхода в свет 6 номера нашего журнала за 2003 год, где была опубликована предыдущая статья, рассказывающая о новой версии VS (кодовое имя Whidbey), Microsoft определился с датой ее выхода и названием.

Новая версия VS должна выйти в 2005-ом году и будет иметь название «Visual Studio 2005». От суффикса .NET решено избавиться. Несколько странное решение, так как поддержка .NET в VS только усилилась, ну, да неисповедимы пути...

IDE

Внешний вид

В новой альфа-версии внешний вид VS претерпел некоторые изменения, в основном косметические. Так, закладки документов стали иметь мако-подобный вид, а неактивные закладки имеют градиентную заливку (из-за этого под Windows 2003 текст на них стал трудно читаем). Изменился внешний вид иконок, указывающих позицию docking-а (см. рисунок 1).


Рисунок 1. Внешний вид новой альфа-версии VS.2005.

Улучшения в редакторе кода

Форматирование кода

В прошлой статье я уже говорил, что в Whidbey значительно расширились возможности автоматического форматирования кода. Причем я сетовал на то, что не все работает так, как хотелось бы. Приятно констатировать, что в новой альфа-версии практически все причины для беспокойства устранены. Так, исправлено форматирование переноса на другую строку параметров и выражений. А от изменения количества пустых строк между элементами кода разработчики VS вообще отказались. Возможно, это и не здорово, но лучше уж никак, чем криво. Неверное автоматическое форматирование даже самого пустякового момента может настолько раздражать, что многие из-за какой-то мелочевки могут вообще отказаться от этой удобной возможности. Кстати, для тех, чей вкус все же не удастся полностью удовлетворить, есть возможность отключить автоматическое форматирование кода. По умолчанию оно осуществляется после вставке знаков «;» и «}». Также при вставке данных из клипборда производится автоматическое форматирование отступов. Каждый из пунктов можно отключить. Правда, по-моему, в этом нет никакой необходимости. Ну, да на вкус и цвет…

В предыдущей версии Whidbey все настройки форматирования были помещены в один огромный список. Найти нужную при этом было очень не просто. Теперь количество настроек несколько уменьшили, и, что очень важно, настройки разбили на разделы (поместив их в подветки). На рисунке 2 изображен диалог настройки форматирования кода. Как видите, настройки форматирования разбиты на Indentation (отступы), New lines (перевод строк), Spacing (вставка пробельных символов), Wrapping (перенос конструкций на другую строку).


Рисунок 2. Опции форматирования кода.

Приятно отметить, что теперь форматирование настраивается просто и быстро.

Ранее я пользовался VisualAssist-ом. Но даже он не давал такого удобства ввода и форматирования кода. Перенос кода сделан настолько интуитивно, что порой создается впечатление, будто редактор угадывает мои мысли. Так, если задать в опциях настройку «Formatting\Wrapping\Leave block on single line if possible», то такая строка:

get{return     _myProp;    }

Будет отформатирована так:

get { return _myProp; }

А такая:

get {
return _myProp; }

так:

get
{
  return _myProp;
}

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

Подсветка синтаксиса

Теперь синтаксис C# подсвечивается не на базе лексического анализа (как раньше), а на базе полноценного синтаксического разбора (парсинга). Это позволяет подсвечивать отдельные ключевые слова и отличать контекстно-используемые (такие, как get, set, add, remove, value) ключевые слова от идентификаторов, имеющих то же имя. Так что теперь редактор будет подсвечивать эти слова только там, где они имеют значение ключевых слов. Так же IntelliSense будет показывать их только в тех местах, где они допустимы.

Ассоциирование типов файлов с редакторами

В VS 6.0 набор поддерживаемых языков программирования был неизменен, но в свойствах редактора (открываемых при нажатии Ctrl + Enter) можно было легко переключить тип редактируемого файла. Это, например, позволяло при редактировании файлов грамматики построителей парсеров (вроде yacc-а и CocoR) переключаться в режим редактирования C++, что упрощало редактирование встраиваемых участков кода. К сожалению, в VS 2002 (7.0) такая возможность исчезла. Зато в VS 2002/2003 появилась возможность наращивать список поддерживаемых языков и дизайнеров. Правда, реализовать такую поддержку не так-то просто. Для этого нужно подключиться к программе VISP и написать немало кода (довольно сложного). Если учесть, что ранее для подключения к программе VISP нужно было заплатить довольно много денег, то идея подключения собственного языка к VS 2002/2003 выглядела совсем нереальной. На сегодня подключение к программе VISP происходит бесплатно, и практически в автоматическом режиме, однако для подключения новых форматов файлов к VS по-прежнему нужен большой объем знаний и немалый объем работы.

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

В VS 2005 появилась настройка ассоциации расширений с имеющимися редакторами и дизайнерами. Эта настройка доступна в диалоге Options в ветке Text Editor\File Extension (см рисунок 3).


Рисунок 3. Диалог ассоциации расширений с редакторами/дизайнерами.

Список поддерживаемых по умолчанию (в поставке VS 2005) редакторов и дизайнеров представлен на рисунке 4.


Рисунок 4. Список поддерживаемых по умолчанию редакторов и дизайнеров.

Свойства проекта теперь открываются как немодальное окно

Небольшой, но смелый эксперимент, предпринятый разработчиками VS. Теперь владельцы больших экранов смогут редактировать свойства нескольких проектов одновременно (см. рисунок 5). :)


Рисунок 5. Новый интерфейс редактирования свойств проекта.

В общем-то, совершенно логичное решение. Очень часто появляется необходимость перейти из интерфейса настройки свойств проекта в какое-то другое окно VS (например, чтобы скопировать настройки из другого проекта). Ранее это было затруднительно.

Изменения в Class View

Во-первых, теперь Class View разбит на два окна (см. рисунок 6). В первом выводится иерархия типов, а во втором – список членов активного типа. Это позволяет упростить навигацию по типам, так как члены типов больше не мешаются под ногами, и нет необходимости в многокилометровом скролинге.


Рисунок 6. Class View.

Во-вторых, в Class View появился поиск. Если в окошко с надписью <Search> ввести некую строку и нажать Enter, то Class View превратится в список, состоящий из типов, их членов или пространств имен, в названиях которых встретилась введенная строка. При этом искомая подстрока подсвечивается другим цветом (см. рисунок 7).


Рисунок 7. Поиск в Class View.

Новый редактор ресурсов

Managed-проекты обзавелись новым редактором ресурсов. Честно говоря старый даже язык не поворачивался называть редактором ресурсов. Он всего лишь позволял редактировать содержимое resx-файлов. Новый редактор позволяет управлять картинками форматов bmp, png, gif, jpeg и tiff. При этом сама VS позволят редактировать только bmp. Для остальных открывается внешний редактор. По умолчанию используется редактор, ассоциированный с расширением файла в Windows, но можно выбрать пункт меню «Open With…», где и настроить любой подходящий редактор (например Photoshop).

Поддерживается хранение файлов (любых форматов), иконок и строк, а также управление ими. Поддерживаются также и любые другие ресурсы (например, потоки, созданные компонентами вроде ImageList), но они практически не поддаются управлению. Этот режим предназначен исключительно для отображения списка ресурсов, не поддающегося управлению из редактора ресурсов (управлением такими ресурсами по-прежнему заведуют дизайнеры компонентов). Внешний вид нового редактора ресурсов можно увидеть на рисунке 8.


Рисунок 8. Редактор managed-ресурсов.

Code Definition View

В новой версии VS появится довольно необычное окно – «Code Definition View». Оно похоже на окно редактора кода, только код, отображающийся в нем, не редактируется и зависит от контекста, на котором стоит курсор в обычном редакторе кода. Так, если встать на имя переменной, то в Code Definition View будет показан участок кода, в котором определена эта переменная. А если установить курсор на переменную или тип, исходного кода которых нет, то в Code Definition View отобразится код их описания, восстановленный по мета-информации, получаемой из бинарных сборок.

Object Test Bench

В VS появилось новое окно Object Test Bench. Честно говоря, что оно должно делать, можно только догадываться, но похоже, что это – нечто, связанное с технологией «Разработки на базе тестирования» (TDD). В имеющейся у меня альфа-версии это окно, видимо, не работает (или я не смог с ним совладать).

Find symbol

Find symbol – это новая замечательная возможность, появившаяся в VS. Суть ее очень проста. Она позволяет найти некоторое имя в программе. При этом поиск можно производить в исходниках проекта и сборках (как тех, на которые ссылается проект, так и любых других, включая системные). Эта возможность доступна в окне поиска, вызываемом по Ctrl+F (см. рисунок 9).


Рисунок 9. Диалог поиска имен (Find symbol).

Чем же отличается Find symbol от обычного поиска? Дело в том, что Find symbol выводит результаты, сгруппированные по логическим именам программы. Это значит, что если в программе одно и тоже имя используется для именования разных переменных, типов данных и т.п., то Find symbol сгруппирует все вхождения относительно них.

Так поиск имени «_test» в приложении (см. рисунок 10) нашел две переменных-члена в двух разных классах (они являются основаниями ветвей), и несколько вхождений этих переменных. Причем все вхождения сгруппированы по переменным, к которым они имеют отношение.


Рисунок 10. Результат поиска имени _test.

Совершая двойные щелчки по этим вхождениям можно переходить к соответствующим местам в файлах (или переходить в Object Browser, если исходный код отсутствует).

Если нужны вхождения конкретной переменной, то можно нажать на одном из них правую кнопку мыши и выбрать из контекстного меню пункт «Find All Reference».

Думаю, вряд ли нужно объяснять, насколько полезна данная возможность. Наряду с рефакторингом, она может резко поднять продуктивность программиста.

Улучшен Object Browser

Object Browser научился показывать информацию об атрибутах, ассоциированных с объектом (в том числе и AttributeUsageAttribute у самих атрибутов). Так что теперь исследовать чужие сборки будет легче.

Кроме этого, в Object Browser упрощен поиск. Теперь для этого не нужно открывать отдельное диалоговое окно. Достаточно вбить текст в текстовое окошко расположенное над деревом объектов (см. рисунок 12).


Рисунок 12. Object Browser.

Кстати, теперь при попытке перейти к описанию типа, для которого не имеется исходных кодов, VS не открывает Object Browser, а формирует файл, содержащий описание этого типа. Так что я в первый раз даже не понял, что произошло. В закладке при этом вместо имени файла пишется название класса и надпись «[from metadata]». Жаль, что при этом не восстанавливаются описания конструкторов (надеюсь, к релизу это упущение будет исправлено). Кроме того, жаль, что при этом не производится декомпиляция. :)

Insert Expression

В прошлой статье я рассказывал о том, что в VS появилась возможность вставки шаблонных участков кода (code snippet). Я тогда сказал, что эта возможность расширяема, но посетовал, что не смог найти, как это делается. Так вот теперь точно стало известно, что они будут расширяться, так как появился пункт «My code snippets». Нашлось место, где должны лежать предопределенные шаблоны. Они лежат в каталоге VS2005\VC#\Expansions\1033\Expansions\. VS2005 – это каталог, в который установлена Visual Studio.

Каждый шаблон описывается в отдельном XML-файле. Вот так, например, выглядит файл, содержащий шаблон для создания enum-а:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0">
    <Header>
        <Title>enum</Title>
        <Shortcut>enum</Shortcut>
        <Description>Expansion snippet for enum</Description>
        <SnippetTypes>
            <SnippetType>Expansion</SnippetType>
            <SnippetType>SurroundsWith</SnippetType>
        </SnippetTypes>
    </Header>
    <Snippet>
        <Declarations>
            <Literal default="true">
                <ID>name</ID>
                <ToolTip>Enum name</ToolTip>
                <Default>MyEnum</Default>
            </Literal>
        </Declarations>
        <Code Language="csharp" Format="CData"><![CDATA[enum $name$
    {
            $selected$ $end$
    }]]>
    </Code>
    </Snippet>
</CodeSnippet>

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

Debug

Окно Immediate

Я уже говорил об этом окне в предыдущей статье. Но как следует его не попробовал (возможно, потому, что оно тогда работало еще не очень хорошо). Опробовав его во второй раз, я нашел его довольно удобным. В нем можно даже определять динамические переменные (прямо во время выполнения, без перекомпиляции). Такие переменные показываются в отладчике с префиксом «$». В окне Immediate можно производить любые вычисления и вызывать любые методы. Единственное, что отличает его от аналогичного окна в VB 6 – это то, что перед его использованием программу нужно скомпилировать.

Окно Immediate может упростить отладку и ускорить процесс изучения новых классов.

Точки останова

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


Рисунок 14. Действия, выполняемые на точках останова.

Если установить флажок «Continue execution», то иконка точки останова изменится на ромбик.

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

В точках останова появилась и еще одна возможность – ограничений «Constraints» (см. рисунок 15). Однако что это, и с чем его едят, не совсем понятно. К сожалению, Help не подключен к диалогам, связанным с точками останова.


Рисунок 15.Окно ограничений точек останова.

Хинты

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

Изменился и внешний вид хинтов. Они стали несколько аскетичнее и компактнее. Впрочем, они могут поменять свой вид еще не один раз до релиза.

Атрибуты, связанные с отладкой

В .NET Framework 2.0 появятся несколько атрибутов, позволяющих упростить отладку приложения.

Атрибут DebuggerTypeProxy

Атрибут DebuggerTypeProxy позволяет изменить представление объекта в окне Watch отладчика. Он заставляет отладчик вместо класса отображать указанный в первом параметре атрибута прокси-объект. Прокси-объект должен иметь публичный конструктор с одним параметром, тип которого совпадает с типом, которому назначен атрибут.

Вот пример, демонстрирующий создание прокси для класса Test:

[DebuggerTypeProxy(typeof(TestProxy))]
class Test
{
  public int _i = 123;
}

class TestProxy
{
  Test _test;

  public TestProxy(Test test)
  {
    _test = test;
  }

  public int Val { get { return _test._i * 10; } }
}

Если теперь посмотреть под отладчиком значение объекта Test, то оно будет представлено объектом TestProxy (см. рисунок 16).


Рисунок 16. Отображение содержимого объекта с помощью прокси.

При этом содержимое исходного значения можно увидеть в псевдо-поле «Raw view».

Прокси могут производить обработку любой сложности, а так же изменять содержимое объекта.

На момент написания этой статьи в .NET Framework прокси были созданы для ArrayList, HashTable, KeyedCollecton, Queue, SortedListStack. Это позволяет видеть более удобоваримое представление экземпляров этих классов в отладчике. Например, хэш-таблица, заполненная следующим образом:

Hashtable ht = new Hashtable();
ht["Мапа"] = 1;
ht["Папа"] = 2;
ht["Я"]    = 3;

Будет выглядеть в отладчике так, как показано на рисунке 17.


Рисунок 17. Упрощенное представление хэш-таблицы, созданное с помощью прокси-класса и атрибутов DebuggerTypeProxy и DebuggerDisplay.

К сожалению, для типизированных (generic) коллекций прокси нет. Возможно, что это не недоработка, а некоторая непродуманность, так как в соответствии со стандартом C# 2.0 в атрибутах нельзя ссылаться на generic-типы. Так что отображение их в отладчике оставляет желать лучшего. Будем надеяться, что к релизу разработчики найдут какое-то решение, и с generic-типами в отладчике будет работать так же удобно, как с обычными.

Атрибут DebuggerDisplay

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

[DebuggerDisplay("Test: _i = {_i}")]
[DebuggerTypeProxy(typeof(TestProxy))]
class Test
...

то в отладчике объект Test будет выглядеть так, как показано на рисунках 18 и 19.


Рисунок 18. Изменение краткого представления объекта с помощью атрибута DebuggerDisplay.


Рисунок 19. Отображение значения объекта Test в хинте отладчика.

Атрибуты DebuggerDisplay и DebuggerTypeProxy можно подключать не только к своим типам, но и к чужим (даже если вы не имеет их исходников). Для этого их нужно объявить глобально (с AttributTarget, равным assembly). Например, с помощью следующего объявления:

[assembly: 
  DebuggerDisplay("FileStream: {Name}", 
  Target = typeof(System.IO.FileStream))
]

можно заставить VS отображать имя файла для объектов типа FileStream (рисунки 20 и 21).


Рисунок 20. Отображение информации об объекте FileStream по умолчанию.


Рисунок 21. Отображение информации об объекте FileStream после добавления атрибута DebuggerDisplay.

В общем, все просто и красиво. Можно сказать – магия! Причем если вы попытаетесь повторить мой фокус (впрочем, как и любой другой фокус уважающего себя мага), у вас ничего не получится. :) А все потому, что главное в любой магии – знать секрет! В данном случае это путь к каталогу «C:\VS2005\Common7\Packages\Debugger\attribcache\» (естественно, C:\VS2005 вам придется заменить на путь, по которому у вас располагается VS). Сборку, содержащую глобальные атрибуты и классы, на которые она ссылается, нужно скопировать в этот каталог. Естественно, сборка при этом должна быть библиотечной (DLL-ю).

DebuggerVisualizer

Если помните, в прошлой статье я рассказывал, о том, что появилась замечательная возможность просматривать строковые переменные в большом окне. При этом в отладчике рядом со значением строки появляется кнопка с лупой, нажав на которую можно открыть окно просмотра.

Такие окна разработчики VS назвали визуализаторами (Visualizers). Отрадно отметить, что визуализаторы можно создавать для любого типа данных (правда пока не ясно, получится ли создавать их для generic-типов).

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

  1. Создать класс, реализующий интерфейс IDebugVisualizer. Этот интерфейс содержит всего один метод – Show. В нем нужно открыть диалог, визуализирующий данные.
  2. Если объект, информацию которого нужно отображать, не поддерживает сериализацию, нужно создать наследника класса VisualizerObjectSource, и в его методе GetData реализовать сериализацию для этого объекта.
  3. Зарегистрировать визуализатор с помощью атрибута DebuggerVisualizer. Если при этом используется ручной сериализатор, нужно его указать.

Если атрибут применяется глобально, то, как и в предыдущих случаях, он и все, что с ним связано, должно быть помещено в отдельную сборку. Эту сборку после компиляции нужно скопировать в каталог «C:\VS2005\Common7\Packages\Debugger\attribcache\».

Описание всех необходимых интерфейсов и классов находится в сборке C:\VS2005\Common7\IDE\meehost.dll, так что ее необходимо подключить к проекту, в котором реализуется визуализатор. Это небольшая сборка, но тащить лишнее за своим проектом я бы не стал, поэтому, думаю, что лучше всего регистрировать визуализаторы с помощью глобальных атрибутов (именно так и поступили программисты из Microsoft, реализуя единственный managed-визуализатор для класса DataSet).

Чтобы лучше разобраться в вопросе, как и с чем едят визуализаторы, я создал визуализатор для объектов класса System.Windows.Forms.ImageList. Этот визуализатор выводит диалог, содержащий элемент управления ListView, в который добавляются элементы, соответствующие картинкам ImageList-а. Названия элементов формируются из их порядкового номера и ключа (если таковой задан).

ImageList не поддерживает сериализации, поэтому пришлось реализовать для него ручную сериализацию, создав наследника класса ImageListVisualizerSource:

class ImageListVisualizerSource : VisualizerObjectSource
{
  public override void GetData(object obj, System.IO.Stream stream)
  {

    ImageList imgList = (ImageList)obj;
    if (imgList == null)
      throw new ArgumentException("The object must be a ImageList!", "obj");

    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(stream, imgList.ImageStream);
  }
}

Как видите, я воспользовался свойством ImageStream, которое сериализует состояние ImageList-а в поток.

Далее я создал класс, реализующий интерфейс IDebugVisualizer.

class ImageListVisualizer : IDebugVisualizer
{
  #region IDebugVisualizer Members
  public void Show(
    IServiceProvider service,
    IVisualizerObjectProvider objectProvider,
    VisualizerUIType uiType)
  {
    if (uiType == VisualizerUIType.Modal)
      ShowModal(
        (IModalDialogVisualizerService) service.GetService(
        typeof(IModalDialogVisualizerService)),
        objectProvider);
    else
      throw new ArgumentException("Only modal visualizer supported");
  }
  #endregion

  void ShowModal(
    IModalDialogVisualizerService service,
    IVisualizerObjectProvider objectProvider)
  {
    if (service == null)
      throw new ApplicationException(
        "This debugger does not support modal visualizers");

    ImageListForm form = new ImageListForm();
    form.ObjectProvider = objectProvider;
    service.ShowDialog(form);
  }
}

Как видите, реализация довольно проста. Я создаю форуму, передаю ей (через свойство ObjectProvider) ссылку на интерфейс IVisualizerObjectProvider (с помощью которого можно получить доступ к визуализируемому объекту) и открываю диалог с помощью сервиса IModalDialogVisualizerService.

Сам диалог – это обычная форма, созданная редактором форм VS. На ней находится только один элемент управления – созданный мной UserControl.

Этот элемент управления содержит в себе два других ListView (с именем _listView) и ImageList (с именем _imgList). _imgList подключен к ListView через свойства LargeImageList и SmallImageList.

Вся инициализация производится в свойстве ObjectProvider:

IVisualizerObjectProvider _objectProvider;

public IVisualizerObjectProvider ObjectProvider
{
  get { return _objectProvider; }
  set
  {
    _objectProvider = value;
    if (_objectProvider == null)
      return;
    // Очищаем _listView
    _listView.Items.Clear();
    // Получаем поток, содержащий сериализованное представление ImageList-а
    // и инициализируем им объект _imgList.
    _imgList.ImageStream = (ImageListStreamer)_objectProvider.GetObject();

    if (_imgList.Images.Count <= 0)
      return;

    _listView.BeginUpdate();
    try
    {
      // Перебираем все картинки ImageList-а и добавляем в ListView 
      // по одному элементу для каждой из них.
      // При этом ассоциируем картинки с элементами и формируем имя
      // элементов из их порядкового номера и ключа.
      for (int i = 0, len = _imgList.Images.Count; i < len; i++)
        _listView.Items.Add(new ListViewItem("#" + i + " " 
          + _imgList.Images.Keys[i], i));
    }
    finally
    {
      _listView.EndUpdate();
    }
  }
}

Теперь остается только зарегистрировать визуализатор и сериализатор с помощью атрибута DebuggerVisualizer:

[assembly:
  DebuggerVisualizer(
    typeof(Rsdn.Visualizers.ImageListVisualizerSource),
    typeof(Rsdn.Visualizers.ImageListVisualizer),
    VisualizerUIType.ToolWindow,
    Target = typeof(System.Windows.Forms.ImageList), 
    Description = "View images...")
]

Остается скомпилировать сборку и скопировать ее в указанный выше каталог.

Кстати, обратите внимание на именованный параметр Description. В нем задается текст, который будет выводиться в меню, выпадающем при нажатии на кнопку визуализатора.

Если нигде не ошибиться, то у любой переменной или свойства типа ImageList должна появиться кнопочка визуализатора, при нажатии на который выпадает меню, содержащее список визуализаторов (их ведь может быть много). На рисунках 22 и 23 показаны кнопка с открытым при ее нажатии меню и сам визуализатор (соответственно).


Рисунок 22. Кнопка визуализатора и меню, открывающееся при ее нажатии.


Рисунок 23. Визуализатор ImageList-а.

DebuggerBrowsableAttribute

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

Применимость отладочных атрибутов

Атрибут DebuggerDisplay может быть применен к сборке, классу, структуре, перечислению или свойству.

Атрибуты DebuggerTypeProxy и DebuggerVisualizer могут быть применены только к сборке, классу или структуре.

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

Новое в CLR

Изменений в CLR практически нет. В основном они сводятся к улучшениям производительности и приведением CLR в соответствие с новым стандартом C# версии 2.0.

Как я и предполагал, программисты Microsoft реализовали обещанное в стандарте устранение боксинга при вызове методов «интерфейсов, описанных в уточнениях (Constraint-ах)». Дело в том, что это с одной стороны создавало некоторые алгоритмические проблемы, а с другой – снижало производительность.

Производительность снижалась из-за того, что при вызове метода интерфейса производится виртуальный вызов (вызов по ссылке). При этом с одной стороны происходят лишние (во многих случаях) вычисления адреса метода, а с другой стороны, JIT-компилятор не может производить автоматический inlining методов.

Как я и предполагал при устранении боксинга так же устраняется и косвенность вызова. При этом появляется возможность автоматического inlining-а и других оптимизаций, не доступных при виртуальных вызовах.

Переписав свои тесты (из 6-го номера нашего журнала за прошлый год) с использованием компаратора, реализованного в структуре, я добился производительности generic-функции быстрой сортировки массива целых чисел, практически неотличимой от аналогичного алгоритма, реализованного на шаблонах C++ и скомпилированного VS 8 (порождаемый которым код является одним из наиболее быстрых на сегодня для PC). Ниже приведен код этого метода и компаратора. Обратите внимание на участки кода, выделенные красным.

interface IComparer<T>
{
  bool Great(T a, T b);
}

struct ComparerInt : IComparer<int>
{
  public bool Great(int a, int b)
  {
    return a > b;
  }
}

public static void Sort2<T, Cmp>(
  Cmp comparer, 
  T[] items,
  int left, 
  int right) where Cmp : IComparer<T>
{
  int i = left;
  int j = right;
  T center = items[(left + right) / 2];

  while (i <= j)
  {
    while (comparer.Great(center, items[i]))
      i++;
    while (comparer.Great(items[j], center))
      j--;

    if (i <= j)
    {
      T x = items[i];
      items[i] = items[j];
      items[j] = x;
      i++;
      j--;
    }
  }

  if (left < j)
    Sort2(comparer, items, left, j);
  if (right > i)
    Sort2(comparer, items, i, right);
}

Скорость выполнения этого кода была на несколько процентов хуже VC 8 и была выше чем множество других компиляторов C++. Это внушает надежу, что в скором времени за простоту, скорость и надежность разработки не придется платить скоростными характеристиками получаемого ПО.

Изменения в C#

Static-классы

Часто случалось так, что класс содержал только статические методы. При этом чтобы запретить его создание, программисты шли на следующую хитрость – делали конструктор скрытым (private). Создатели языка решили узаконить эту практику и разрешить использование ключевого слова static для класса. Static-классы не могут иметь не статических членов, что приводит к усилению контроля компилятора над корректностью кода.

Nullable-типы

В прошлой статье я рассказывал о появлении типа Nullable и сетовал, что его нет в имевшейся у меня на тот момент реализации .NET Framework (версии 1.2). В новой версии (2.0) этот класс появился, и о нем можно рассказать более подробно.

Первое, что нужно сказать – для объявления Nullable-типов в C# появился специальный синтаксис. Теперь, если после имени типа добавить знак «?», он будет интерпретироваться компилятором как Nullable-тип. Таким образом, записи:

int? i = null;

и:

Nullable<int> i = null;

идентичны.

Nullable-тип позволяет хранить в себе значение value-типа и признак «null». Если присвоить переменной такого типа null, то ее сравнение с null будет возвращать true, а попытка приведения к базовому типу приведет к возникновению исключения.

int? i = null;

Console.WriteLine("{0}", i == null ? "i is null" : "i is " + i);
Console.WriteLine(i.GetType().Name);
Console.WriteLine("sizeof = {0}", Marshal.SizeOf(i));

Генерируемый компилятором код при инициализации переменной значением null:

Nullable<int> nullable1;
Nullable<int> nullable2;
i = 0;
nullable2 = 0;

Console.WriteLine("{0}", 
  i == nullable2 ? "i is null" : string.Concat("i is ", I  ));
Console.WriteLine(i.GetType().Name);
Console.WriteLine("sizeof = {0}", Marshal.SizeOf(i));

Вывод на консоль:

i is null
Nullable!1
sizeof = 8

Генерируемый компилятором код при инициализации переменной значением “0”:

Nullable<int> nullable2;
Nullable<int> i = Nullable<int>.op_Implicit(0);
nullable2 = 0;

Console.WriteLine("{0}", 
  i == nullable2 ? "i is null" : string.Concat("i is ", i));
Console.WriteLine(i.GetType().Name);
Console.WriteLine("sizeof = {0}", Marshal.SizeOf(i));

Вывод на консоль:

i is 0
Nullable!1
sizeof = 8

Тип данных Nullable имеет свойство Value, возвращающее хранимое значение, и свойство HasValue, позволяющее узнать, можно ли получить хранимое значение. Для этого типа реализованы операции неявного (при приведении к Nullable-типу) и явного (в обратном случае) приведения типов.

int? i = 0;
int i2;
if (i.HasValue)
  i2 = i.Value;

Тип данных Nullable и его поддержка компилятором C#, несомненно, введены для упрощения работы с БД. Однако пока что он практически нигде не используется. Мне удалось обнаружить его использование только в методе System.Array.Find<T>:

public static Nullable<T> Find<T>(T[] array, Predicate<T> match);

Где он используется в случае, если метод ничего не нашел.

Кстати, примечательно то, что появление generic-ов сразу привело к введению в FCL функционального подхода. Непонятно, о чем я? Дело в том, что второй параметр этой функции – это делегат, объявленный следующим образом:

public delegate bool Predicate<T>(T obj)

Таким образом, данный вариант Find инкапсулирует логику поиска, вызывая для проверки предиката метод, передаваемый ему в качестве параметра. Такой подход свойствен функциональным и логическим языкам, а также зачастую используется в C++ (только там вместо делегатов используются фанкторы и указатели на функции). Честно говоря, из-за использования делегатов такой подход порождает более медленный код, но потенциально эту проблему может устранить JIT-компилятор. Пока что такие оптимизации ему не по зубам, но в будущем они обязательно должны быть введены в среды, подобные .NET.

Ограничения struct и class

В C# добавлены два новых вида ограничений (Constraints) – struct и class. Это позволяет еще на стадии разработки generic-типа или метода определить, что в нем можно использовать только value-типы или только ссылочные типы. Value-типы описываются ключевым словом struct, а ссылочные типы – ключевым словом class. Честно говоря, несколько некорректное использование ключевых слов, так как value-типы и структуры – это не совсем одно и тоже. Это наводит на мысль, что кое-что в языке еще может измениться.

Чтобы вы смогли лучше понять, о чем идет речь, приведу два примера. В первом используется constraint class:

class B<T> where T : class
{
  //...
}

static void Main()
{
  B<string> b1 = new B<string>(); // OK
  B<int>    b2 = new B<int>();    // Ошибка во время компиляции
}

Это приводит к тому, что в качестве аргументов типа не удастся передать value-тип.

Второй использует constraint struct и наоборот, не позволяет использовать ссылочные типы в качестве аргументов типа:

class B<T> where T : struct
{
  // ...
}

static void Main()
{
  B<int>    b1 = new B<int>();    // OK
  B<string> b2 = new B<string>(); // Ошибка во время компиляции
}

Массивы фиксированного размера

В unsafe-режиме появилась возможность определять в структурах и классах массивы фиксированного размера. Эта возможность получила название «unsafe buffers» или «fixed-size buffers». Она позволяет упростить интеграцию с унаследованными API и повысить производительность Interop-кода.

Фиксированные массивы должны иметь один из следующий типов элементов: byte, char, short, int, long, sbyte, ushort, uint, ulong, float или double. Массивы обязаны быть одномерными, и их нельзя передавать в функции, требующие ссылки на обычный массив. Имя такого массива аналогично указателю на его первый член. Другими словами – это аналог массива в С. Вот пример, демонстрирующий использование массивов фиксированного размера:

unsafe struct A
{
  fixed int _ary[10];

  static void Main()
  {
    A a;
    a._ary[100] = 123;
    Console.WriteLine("a._ary[100] = {0}", a._ary[100]);
  }
}

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

Разный уровень доступа для accessor-ов свойств

В предыдущей версии Whidbey VB.NET поддерживал задание разных модификаторов доступа для set и get accessor-ов. В новой версии это стало возможным и в C#. Задать другой модификатор доступа можно только для set или get accessor-ов. При этом можно только понижать уровень доступа. Вот как это выглядит:

...
public string MyProp
{
  get { return _myProp; }
  internal set { _myProp = value; }
}

extern alias

В новой версии VS появилась возможность задавать алиасы для сборок. При подключении ссылки на сборку для нее можно задать алиас (альтернативное имя). Это позволяет избегать конфликтов имен и использовать в одном проекте ссылки на разные версии одной и той же сборки. Сам алиас задается или в параметре reference:

csc /reference:MyAliace=SomeAssemblyName.dll some.cs

или в свойствах ссылок на внешние сборки (References) VS (см. рисунок 24). Правда, пока что это работает не совсем корректно. VS начинает видеть алиас только после перезапуска. Но это явно временная недоработка, и она должна быть устранена к релизу.


Рисунок 24. Присвоение алиаса сборке.

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

В коде проекта, на сборку, подключенную подобным образом, нужно ссылаться с использованием ее алиаса. Например, сборку с алиасом MyAliace можно объявить следующим образом:

extern alias MyAliace;

А так можно использовать типы из этой сборки:

MyAliace.System.Data.DataTable tbl = new MyAliace.System.Data.DataTable();
tbl.TableName = "MyTable";

Доступ к алиасам пространств имен

В C# 2.0 введен новый оператор доступа к алиасам пространств имен «::». Очевидно, что этот оператор позаимствован из C++, однако в C# он работает несколько иначе. Оператор «::» позволяет обращаться только к алиасам пространств имен, созданных с помощью инструкции using:

using Txt = System.Text;
...
Txt::StringBuilder sb = new Txt::StringBuilder();

С помощью этого оператора запрещается обращаться к классам или реальным пространствам имен.

Имеется предопределенный алиас «global», позволяющий обратиться к глобальному пространству имен. Это позволяет избегать конфликтов, если имеются пространства имен или классы, имена которых дублируют глобальные пространства имен:

global::System.Text.StringBuilder sb2;

Заключение

Новые версий VS и .NET Framework делают процесс программирования настолько приятным и увлекательным, что не замечаешь, как бежит время. Программирование становится приятным развлечением или хобби. А что может быть лучше, чем получать деньги за собственное хобби? :)


Эта статья опубликована в журнале RSDN Magazine #2-2004. Информацию о журнале можно найти здесь
    Сообщений 27    Оценка 385 [+1/-0]         Оценить