Знакомство с Microsoft .NET Framework
Часть 2. Common Language Runtime
В предыдущем номере мы начали знакомство с Microsoft .NET Framework — ключевым компонентом Microsoft .NET, представляющим собой платформу для создания, внедрения и выполнения Web-сервисов и приложений. Мы рассмотрели основные компоненты Microsoft .NET Framework и кратко описали их назначение. Мы также начали более подробное рассмотрение Common Language Runtime (CLR) — среды выполнения .NET-приложений. В этой части статьи мы продолжим разговор о Common Language Runtime и остановимся на Common Type System.
Common Type System
ommon Type System (CTS) — это последний компонент Common Language Runtime, который мы рассмотрим в данной статье. CTS определяет типы, поддерживаемые Common Language Runtime. Типы можно условно разделить на две большие группы: данные со значениями (value types) и ссылочные типы (reference types). В каждой группе существуют подтипы (см. рис. 1).
Данные со значениями описывают значения, представляемые последовательностью байтов. Для таких типов существует понятие идентичности. Например, 32-битное целочисленное значение занимает определенную последовательность байтов. Два 32-битных целочисленных значения идентичны, если они содержат одно и то же число. К данным со значениями также относятся встроенные типы, рассматриваемые ниже. Ссылочные типы служат для описания значений, представляемых местонахождением последовательности байтов. Ссылочные типы подразделяются на три категории:
- Объектные типы — содержат самоописываемые значения. Некоторые объектные типы (абстрактные классы) содержат только частичное описание значений.
- Интерфейсные типы — всегда содержат только частичное описание значений.
- Указательные типы — во время компиляции содержат описание значения, представленного адресом в памяти.
Составной частью Common Type System являются встроенные типы (табл. 1), непосредственно поддерживаемые виртуальной исполняющей системой (Virtual Execution System, VES).
мы рассмотрим типы и их подтипы более подробно, но прежде отметим, что в основе всех типов, как, впрочем, и в основе всей иерархии классов Microsoft .NET Framework, лежит класс System.Object. Для всех типов, которые будут рассмотрены ниже, этот класс, в частности, предоставляет методы, перечисленные в табл. 2.
Далее мы сможем убедиться, что все типы так или иначе наследуют от System.Object. Начнем с данных со значениями.
Данные со значениями
ак мы отметили выше, к данным со значением относятся как встроенные типы так и определяемые пользователями. Все встроенные типы базируются на классе System.ValueType, который является непосредственным наследником System.Object. Каждый класс, реализующий встроенный тип, поддерживает определенный набор интерфейсов, которые используются для реализации тех или иных операций, а также уникальные для данного типа свойства и методы. Например, класс Boolean поддерживает интерфейсы IConvertible (для выполнения операций преобразования) и IComparable (для выполнения операций сравнения), а также содержит такие свойства, как FalseString и TrueString. Кроме того, класс Byte поддерживает интерфейсы IConvertible и IComparable, а также интерфейс IFormattable, используемый для преобразования значений в их строчное представление. Двигаясь дальше по списку наследников класса System. ValueType, мы находим класс Char, который поддерживает такие методы, как IsDigit, IsLetter, IsLetterOrDigit, IsNumber и т.п., существенно облегчающие работу с символами.
Классы, представляющие целочисленные типы — Int16, Int32, Int64, IntPtr, UInt16, UInt32, UInt64 и UIntPtr, содержат константы MinValue и MaxValue, задающие минимальное и максимальное число, которое может содержаться в данном типе, а также метод Parse, используемый для преобразования строки, состоящей из набора цифр, в соответствующий тип данных.
Типы с плавающей точкой — Single и Double — содержат свойства Epsilon (минимальное положительное число данного типа), MinValue (минимально допустимое число), MaxValue (максимально допустимое число), Nan (константа Not-A-Number, не число), NegativeInfinity (константа, описывающая отрицательную бесконечность) и PositiveInfinity (константа, описывающая положительную бесконечность), а также методы IsInfinity, IsNan, IsNegativeInfinity и IsPositiveInfinity, используемые для проверки хранимых в переменной данного типа значений.
Перечисления
Среди разнообразия типов, которые могут определять пользователи, отметим перечисления, создающиеся на базе класса System. Enum. Этот класс поддерживает интерфейсы IComparable, IFormattable и IConvertible, а также методы для получения имен констант (GetName, GetNames), получения значений (GetValues), определения типа элементов перечисления (GetUnderlyingType) и ряд других.
В следующем примере показано, как создать простое перечисление и воспользоваться методами GetNames и GetValues для получения списка имен всех его полей и их значений:
Module Module1 ' ' Геометрические фигуры ' Public Enum Shapes None = 0 Circle = 1 Rectangle = 2 Ellipse = 3 Triangle = 4 End Enum Sub Main() Dim GeoShapes As New Shapes() Dim Names As String() Dim Values() As Integer Dim I As Integer Names = GeoShapes.GetNames(GeoShapes.GetType) Values = GeoShapes.GetValues(GeoShapes.GetType) Console.WriteLine() For I = 0 To Names.GetUpperBound(0) Console.WriteLine(vbTab & Names(I) & "=" & _ CType(Values(I), Integer).ToString("G")) Next Console.WriteLine() End Sub End Module
Структуры
Структуры позволяют создавать новые типы данных путем объединения нескольких существующих типов. Структуры могут содержать методы и события, но в отличие от классов, которые мы рассмотрим ниже, структуры не поддерживают наследования и инициализации полей.
Рассмотрим небольшой пример. Создадим структуру TPoint, у которой будет два поля: целочисленные члены X и Y. В коде нашей программы, написанной на Visual Basic.NET, создадим переменную типа TPoint, инициализируем ее поля и выведем на экран их значения. Как это сделать, показано ниже:
Module Module1 ' ' Структура TPoint содержит два целочисленных члена '
Structure TPoint Public X As Integer Public Y As Integer
End Structure Sub Main() ' ' Переменная MyPoint – пользовательского типа ' Dim MyPoint As TPoint ' ' Инициализация переменной ' With MyPoint .X = 100 .Y = 150 ' ' Вывод значений ' Console.WriteLine(" X should be 100" & " X=" & .X) Console.WriteLine(" Y should be 150" & " Y=" & .Y) End With End Sub End Module
Теперь скопируем структуру в объектную переменную и изменим значения ее полей. Поскольку структура скопирована, а не передана по ссылке, оригинальные значения не изменяются:
Dim MyPoint As TPoint Dim NewPoint With MyPoint .X = 100 .Y = 150 End With NewPoint = MyPoint With NewPoint .X = 200 .Y = 50 End With Console.WriteLine("NewPoint X, Y=" & NewPoint.X & ", " & NewPoint.Y) Console.WriteLine("MyPoint X, Y=" & MyPoint.X & ", " & MyPoint.Y) End Sub
По сравнению со структурами классы предоставляют большую гибкость. Мы рассмотрим классы в следующем разделе.
Завершая наше знакомство с данными со значениями, напомним, что они описывают значения, представляемые последовательностью байтов. Многие встроенные типы, перечисленные в табл. 1, относятся к этой группе. Данные со значениями часто располагаются на стеке — они могут быть локальными переменными, параметрами и значениями, возвращаемыми функциями. Таким образом, данные со значениями не должны занимать много места — в общем случае они занимают не более 12-16 байтов.
Ссылочные типы
торая группа типов в Common Type System — это ссылочные типы. Среди них есть объектные, интерфейсные и указательные типы. Рассмотрение ссылочных типов начнем с классов.
В общем случае классы и объекты могут содержать поля, свойства, методы и события. Строго говоря, свойства и события являются методами. Свойства — это не более чем синтаксические конструкции, заменяющие обращения к соответствующим методам Set и Get. События служат для асинхронных объявлений об изменениях в объектах. Клиенты предоставляют специальные методы — обработчики событий, которые вызываются при возникновении данного события.
Классы
Классы — это структуры, определяющие операции, которые могут выполнять объекты, а также значения, которые объекты могут хранить. Функциональность объектов (экземпляров класса) доступна через поля и методы, а также через события.
Продолжим наш пример. Теперь мы преобразуем созданную выше структуру в класс, который также будет иметь два целочисленных свойства. Для установки и получения значений этих свойств мы должны использовать ключевое слово Property и новые в VB.NET конструкции языка — Get/End Get и Set/End Set. Мы также создаем конструктор для нашего класса. Реализация класса TPoint показана ниже:
Imports System Module Module1 ' ' Класс TPoint содержит два целочисленных свойства '
Public Class TPoint Dim ptX As Integer ' X Dim ptY As Integer ' Y ' ' Get и Set для свойства ptX ' Public Property X() As Integer Get Return ptX End Get Set(ByVal Value As Integer) ptX = Value End Set End Property ' ' Get и Set для свойства ptY ' Public Property Y() As Integer Get Return ptY End Get Set(ByVal Value As Integer) ptY = Value End Set End Property ' ' Конструктор ' Public Sub New() ptX = 0 ptY = 0 End Sub End Class Sub Main() Dim MyPoint As New TPoint() ' ' Инициализация объекта ' With MyPoint .X = 100 .Y = 150 ' ' Вывод значений ' Console.WriteLine(" X should be 100" & " X=" & .X) Console.WriteLine(" Y should be 150" & " Y=" & .Y) End With End Sub End Module
Здесь следует обратить внимание на то, что внутренние имена свойств класса (в нашем примере ptX и ptY) отличаются от имен, «видимых» вне класса (в нашем примере X и Y).
Методы
Методы служат для реализации функциональности классов. Рассмотрим это на примере. Расширим наш класс и реализуем метод SetXY, который будет устанавливать новые значения свойств X и Y. Для этого добавим в описание класса следующий код:
Public Sub SetXY(ByVal newX As Integer, ByVal newY As Integer) X = newX Y = newY End Sub и изменим код нашей программы: Sub Main() Dim MyPoint As New TPoint() ' ' Инициализация объекта ' With MyPoint .SetXY(100, 150) ' ' Вывод значений ' Console.WriteLine(" X should be 100" & " X=" & .X) Console.WriteLine(" Y should be 150" & " Y=" & .Y) End With End Sub
События
И наконец, последним элементом, который может содержаться в классах, создаваемых пользователями, является событие. Как было отмечено выше, события служат для асинхронных объявлений об изменениях в объектах. В нашем примере таким изменением в объекте будет задание новых значений свойств X и Y.
Реализацию события начнем с задания его имени, сопровождаемого ключевым словом Event. Далее в код, который является причиной возникновения события (в нашем примере это код, устанавливающий новые значения свойств), добавляем ключевое слово RaiseEvent и указываем название возникающего события:
Public Class TPoint Dim ptX As Integer ' X Dim ptY As Integer ' Y Event PTChanged() ' ' Get и Set для свойства ptX ' Public Property X() As Integer Get Return ptX End Get Set(ByVal Value As Integer) ptX = Value RaiseEvent PTChanged() End Set End Property ' ' Get и Set для свойства ptY ' Public Property Y() As Integer Get Return ptY End Get Set(ByVal Value As Integer) ptY = Value RaiseEvent PTChanged() End Set End Property ' ' Конструктор ' Public Sub New() ptX = 0 ptY = 0 End Sub Public Sub SetXY(ByVal newX As Integer, ByVal newY As Integer) X = newX Y = newY End Sub End Class
Проверить работу нашего класса, который теперь имеет свойства, методы и события, можно с помощью небольшого клиентского приложения. Оно содержит кнопку, список и панель. При каждом нажатии кнопки мыши на панели мы вызываем метод SetXY нашего класса Tpoint, в результате чего происходит событие, которое регистрируется нашим обработчиком события в списке.
Вот код клиентского приложения (для простоты класс TPoint реализован в отдельной библиотеке классов):
Imports ClassLibrary1 Public Class Form1 Inherits System.Windows.Forms.Form Dim WithEvents MyPoint As TPoint Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click MyPoint = New TPoint() End Sub Private Sub Panel1_MouseDown(ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles Panel1.MouseDown MyPoint.SetXY(e.X, e.Y) End Sub Private Sub MyPoint_PTChanged() Handles MyPoint.PTChanged ListBox1.Items.Add(MyPoint.X & ", " & MyPoint.Y) End Sub End Class
Теперь объект MyPoint типа TPoint описан с использованием ключевого слова WithEvents. Это означает, что класс поддерживает события и мы можем создавать их обработчики. На рис. 2 показан пример работы нашей программы.
Обратите внимание на то, что в списке регистрируются парные координаты — X, Y, X, Y1, X1, Y1 и т.п. Это происходит из-за того, что событие PTChanged возникает и при изменении значения свойства X, и при изменении значения свойства Y. Таким образом, если одновременно изменяются два свойства, возникают два события. Чтобы исправить логику, достаточно удалить код, генерирующий события, из описаний свойств и добавить его в реализацию метода SetXY, как это показано ниже:
Public Sub SetXY(ByVal newX As Integer, ByVal newY As Integer) X = newX Y = newY RaiseEvent PTChanged() End Sub
Делегаты
Говоря о событиях, следует упомянуть о делегатах — классе на базе System.Delegate, который содержит ссылку на метод. Таким образом, делегат — это указатель на функцию или косвенно-вызываемая функция. Чаще всего делегаты используются для описания обработчиков событий или для создания косвенно-вызываемых функций.
Ниже показано, как изменить код предыдущего примера так, чтобы обработчик события стал делегатом и подключался в момент выполнения программы:
Dim MyPoint As TPoint Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click MyPoint = New TPoint() AddHandler MyPoint.PTChanged, AddressOf MyPoint_PTChanged End Sub Private Sub Panel1_MouseDown(ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles Panel1.MouseDown MyPoint.SetXY(e.X, e.Y) End Sub Private Sub MyPoint_PTChanged() ListBox1.Items.Add(MyPoint.X & ", " & MyPoint.Y) End Sub
Такой подход позволяет нам создавать обработчики событий для более чем одного события, динамически переключать и отключать обработчики событий и т.п.
Массивы
Массивы задаются описанием типа элементов, числа размерностей, и нижней и верхней границ каждой размерности и могут содержать только данные указанного типа. Каждый элемент массива — это объект. массив базируется на классе System.Array, описывающем базовую функциональность массивов. Этот класс поддерживает такие интерфейсы, как ICloneable (реализует возможность создания еще одного экземпляра класса со значениями существующего экземпляра), IList (реализует возможность индивидуальной индексации коллекции объектов), ICollection (реализует возможность задания размеров, создания перечислителей и методов синхронизации) и IEnumerable (поддерживает итерацию по элементам коллекции). Ниже приведены базовые свойства (табл. 3) и методы (табл. 4), поддерживаемые массивами.
В следующем примере показаны некоторые базовые операции с массивами:
Sub Main() Dim IntArray() As Integer = {0, 1, 2, 3, 4, 5} With IntArray Console.WriteLine(vbTab & "Type = " & _ .GetType.ToString) Console.WriteLine(vbTab & "IsFixedSize = " & .IsFixedSize) Console.WriteLine(vbTab & "Length = " & .Length) Console.WriteLine(vbTab & "Rank = " & .Rank) Console.WriteLine(vbTab & "LowerBound = " & _ .GetLowerBound(0)) Console.WriteLine(vbTab & "UpperBound = " & _ .GetUpperBound(0)) End With End Sub
Ниже показано, как использовать методы GetValue и SetValue для получения значений элементов массива и изменения этих значений:
Console.WriteLine(vbCrLf & "Original Array:") For I = .GetLowerBound(0) To .GetUpperBound(0) Console.WriteLine(vbTab & "Array(" & I & ")" & " = " & .GetValue(I)) .SetValue(.GetValue(I) * 10, I) Next Console.WriteLine(vbCrLf & "Modified Array:") For I = .GetLowerBound(0) To .GetUpperBound(0) Console.WriteLine(vbTab & "Array(" & I & ")" & " = " & .GetValue(I)) Next
Интерфейсы
Интерфейсы служат для описания функциональности, которая должна быть реализована тем или иным классом, но они не содержат кода, реализующего данную функциональность. Поскольку интерфейс не содержит кода, его нельзя унаследовать — вместо этого вы наследуете класс, реализующий тот или иной интерфейс.
Интерфейсы широко используются в библиотеке классов Microsoft .NET. Мы уже приводили несколько примеров классов, в которых реализуются такие интерфейсы, как IConvertible (для выполнения операций преобразования) и IComparable (для выполнения операций сравнения)). Полный список интерфейсов, определенных в библиотеке классов Microsoft .NET, представлен в документации
к Microsoft .NET Framework SDK.
Указатели
Common Language Runtime поддерживает три типа указателей. К первому типу относятся управляемые указатели (managed poiters), генерируемые для аргументов методов, которые передаются по ссылке. Это единственный тип указателей, совместимый со спецификацией Common Language Runtime. К двум другим типам относятся неуправляемые указатели (unmanaged pointers) и неуправляемые указатели на функции (unmanaged function pointers). Так как неуправляемые указатели не совместимы с Common Language Runtime, их реализация и поддержка зависят от выбранного языка программирования.
Common Language Runtime поддерживает две операции над управляемыми указателями: получение значения, хранимого по указателю, и запись значения по адресу указателя. Отметим, что хотя указатели являются ссылочными типами, значение типа «указатель» не является объектом, а следовательно, определить точный тип такого значения невозможно.
Заключение
ы ознакомились с одним из основных компонентов Microsoft .NET Framework — средой выполнения Common Language Runtime, рассмотрели такие темы, как исполняемые файлы и метаданные, кратко поговорили о Microsoft Intermediate Language (MSIL), Just-In-Time Compiler, узнали о назначении сборок и глобального кэша сборок (Global Assembly Cache). Затем мы подробно рассмотрели систему типов Common Type System и основные группы типов — данные со значениями и ссылочные типы. Отметим, что поддержка типов на уровне Common Language Runtime обеспечивает простую интеграцию кода, написанного на разных языках программирования. Вопрос о выборе языка программирования — будь то Visual Basic .NET, C# или какой-либо другой — не является первостепенным, поскольку все языки, поддерживающие спецификацию Common Language Runtime, обеспечивают доступ к рассмотренным нами типам. Вопрос лишь в синтаксических особенностях того или иного языка.
Проиллюстрируем вышесказанное. Рассказывая о классах, свойствах, методах и событиях, мы создали VB.NET-класс, реализующий объект TPoint, с двумя свойствами — X и Y и методом SetXY, который позволяет изменять значения этих свойств. При изменении значений свойств X и Y возникает событие PTChanged. Создадим клиентское приложение на C#, которое будет использовать наш VB.NET-класс, и в нем реализуем обработчик события PTChanged. Ниже приведен фрагмент кода, иллюстрирующий необходимые действия:
public class Form1 : System.Windows.Forms.Form { ... private ClassLibrary1.TPoint MyPoint; public Form1() { ... this.MyPoint = new ClassLibrary1.TPoint(); MyPoint.PTChanged += new System.EventHandler(this.MyPoint_PTChanged); } ... private void button1_Click(object sender, System.EventArgs e) { MyPoint.SetXY(10, 150); } private void button2_Click(object sender, System.EventArgs e) { MyPoint.SetXY(100, 250); } private void button3_Click(object sender, System.EventArgs e) { MyPoint.SetXY(50, 100); } void MyPoint_PTChanged(object sender, System.EventArgs e) { MessageBox.Show("X=" + MyPoint.X.ToString("G") + ", Y=" + MyPoint.Y.ToString("G"), "Changed", MessageBoxButtons.OK); } } }
В данном случае необходимо лишь создать Windows-приложение на C#, подключить ссылку на библиотеку классов, в которой описан класс, реализующий объект TPoint, и описать объект типа TPoint:
private ClassLibrary1.TPoint MyPoint;
Теперь вызовем конструктор объекта TPoint и опишем новый обработчик событий:
this.MyPoint = new ClassLibrary1.TPoint(); MyPoint.PTChanged += new System.EventHandler(this.MyPoint_PTChanged);
Сам обработчик событий реализован в следующем методе:
void MyPoint_PTChanged(object sender, System.EventArgs e) { MessageBox.Show( "X=" + MyPoint.X.ToString("G") + ", Y=" + MyPoint.Y.ToString("G"), "Changed", MessageBoxButtons.OK); }
Те, кто когда-либо пробовал использовать в Visual Basic библиотеки для C или интегрировать код на Delphi с кодом на VB, смогут по достоинству оценить изменения, привнесенные Common Language Runtime.
В следующей статье мы начнем ознакомление с библиотекой классов, рассмотрим основные составляющие ее пространства имен и классы, а также приведем некоторые примеры их использования.
КомпьютерПресс 1'2002