oldi

Знакомство с Microsoft .NET Framework

Часть 2. Common Language Runtime

Алексей Федоров

Common Type System

Данные со значениями

   Перечисления

   Структуры

Ссылочные типы

   Классы

   Методы

   События

   Делегаты

   Массивы

   Интерфейсы

   Указатели

Заключение

 

В предыдущем номере мы начали знакомство с 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