public static int myPublicInt; internal
using System; namespace MyNameSpace { public class A { public static int myPublicInt; internal static int myInternalInt; private static int myPrivateInt = 0; public class Nest1 // "Вложенный" член класса { public static int myPublicInt; internal static int myInternalInt; private static int myPrivateInt = 0; } private class Nest2 // "Вложенный" член класса { public static int myPublicInt = 0; internal static int myInternalInt = 0; private static int myPrivateInt = 0; } } public class MyClass { public static int Main() { // Доступ к членам класса A: A.myPublicInt = 1; // Доступ не ограничен A.myInternalInt = 2; // Только в текущем проекте // A.myPrivateInt = 3; - ошибка: нет // доступа вне класса // Доступ к членам класса Nest1: A.Nest1.myPublicInt = 1; // Доступ не ограничен A.Nest1.myInternalInt = 2; // Только в текущем проекте // A.Nest1.myPrivateInt = 3; - ошибка: нет // доступа вне класса Nest1 // Доступ к членам класса Nest2: // A.Nest2.myPublicInt = 1; - ошибка: нет // доступа вне класса A // A.Nest2.myInternalInt = 2; - ошибка: нет // доступа вне класса A // A.Nest2.myPrivateInt = 3; - ошибка: нет // доступа вне класса Nest2 return 0; } } } |
Листинг 15.1. |
Закрыть окно |
using System;
namespace MyNameSpace
{
public class A
{
public static int myPublicInt;
internal static int myInternalInt;
private static int myPrivateInt = 0;
public class Nest1 // "Вложенный" член класса
{
public static int myPublicInt;
internal static int myInternalInt;
private static int myPrivateInt = 0;
}
private class Nest2 // "Вложенный" член класса
{
public static int myPublicInt = 0;
internal static int myInternalInt = 0;
private static int myPrivateInt = 0;
}
}
public class MyClass
{
public static int Main()
{
// Доступ к членам класса A:
A.myPublicInt = 1; // Доступ не ограничен
A.myInternalInt = 2; // Только в текущем проекте
// A.myPrivateInt = 3; - ошибка: нет
// доступа вне класса
// Доступ к членам класса Nest1:
A.Nest1.myPublicInt = 1; // Доступ не ограничен
A.Nest1.myInternalInt = 2; // Только в текущем проекте
// A.Nest1.myPrivateInt = 3; - ошибка: нет
// доступа вне класса Nest1
// Доступ к членам класса Nest2:
// A.Nest2.myPublicInt = 1; - ошибка: нет
// доступа вне класса A
// A.Nest2.myInternalInt = 2; - ошибка: нет
// доступа вне класса A
// A.Nest2.myPrivateInt = 3; - ошибка: нет
// доступа вне класса Nest2
return 0;
}
}
}
using System; public class MyClass1
using System; public class MyClass1 { public static void UseParams1(params int[] list) { // Отображение списка параметров for ( int i = 0 ; i < list.Length ; i++ ) Console.WriteLine(list[i]); } public static void UseParams2(params object[] list) { // В переменном списке параметров могут быть // объекты различных типов for ( int i = 0 ; i < list.Length ; i++ ) Console.WriteLine((object)list[i]); } public static void UseParams3(int k,params object[] list) { // В переменный список параметров // включаются параметры, начиная // со второго for ( int i = 0 ; i < list.Length ; i++ ) Console.WriteLine((object)list[i]); } public static void Main() { UseParams1(1, 2, 3, 4, 5); UseParams1(1, 2); int[] myarray = new int[3] {1,2,3}; UseParams1(myarray); UseParams2(111, 'f', "string"); UseParams3(111, 'f', "string"); } } |
Листинг 15.2. |
Закрыть окно |
using System;
public class MyClass1
{
public static void UseParams1(params int[] list)
{
// Отображение списка параметров
for ( int i = 0 ; i < list.Length ; i++ )
Console.WriteLine(list[i]);
}
public static void UseParams2(params object[] list)
{ // В переменном списке параметров могут быть
// объекты различных типов
for ( int i = 0 ; i < list.Length ; i++ )
Console.WriteLine((object)list[i]);
}
public static void UseParams3(int k,params object[] list)
{ // В переменный список параметров
// включаются параметры, начиная
// со второго
for ( int i = 0 ; i < list.Length ; i++ )
Console.WriteLine((object)list[i]);
}
public static void Main()
{
UseParams1(1, 2, 3, 4, 5);
UseParams1(1, 2);
int[] myarray = new int[3] {1,2,3};
UseParams1(myarray);
UseParams2(111, 'f', "string");
UseParams3(111, 'f', "string");
}
}
Данный пример иллюстрирует консольное приложение,
/* Данный пример иллюстрирует консольное приложение, позволяющее добавлять и отображать элементы структуры. Используемый массив myAB содержит 400 элементов, каждый из которых является структурой из двух полей - name и telfax*/ using System; namespace MyStruct1 { struct AddrBookType { public string name; public string telfax ; } /* Созданная структура определяет новый структурный тип AddrBookType. Элементы структуры объявлены с модификаторами доступа public, так как по умолчанию элементы доступны только внутри структуры */ class Class1 { static void Main(string[] args) { // Создание массива структур AddrBookType[] myAB= new AddrBookType[400]; char icount; char iloop; int i=0; int j=0; while (true) // Запрос кода операции {Console.Write ("Insert kod (0 - new record, 1 - show all, 2 - exit): "); icount = (char)Console.Read (); while (true) // Чтение потока ввода { iloop = (char)Console.Read (); /* Цикл чтения символов будет завершен при нажатии пользователем клавиши Enter и получения из потока ввода символа '\n' */ if (iloop == '\n') break;} if (icount=='2') break; switch (icount) {case '0': Console.WriteLine("Insert Name: "); myAB[i].name =Console.ReadLine (); Console.WriteLine("Insert phone : "); myAB[i].telfax= Console.ReadLine (); i++; // Счетчик введенных элементов break; case '1': // Запись в стандартный поток // вывода for ( j=0; j<i;j++) {Console.Write(myAB[j].name); Console.Write(" "); Console.WriteLine(myAB[j].telfax); } break; default : Console.WriteLine("Ошибка ввода"); break; } // Конец switch } // Конец while } } } |
Листинг 15.3. |
Закрыть окно |
/* Данный пример иллюстрирует консольное приложение, позволяющее добавлять и отображать элементы структуры. Используемый массив myAB содержит 400 элементов, каждый из которых является структурой из двух полей - name и telfax*/
using System;
namespace MyStruct1
{
struct AddrBookType
{ public string name;
public string telfax ;
}
/* Созданная структура определяет новый структурный тип AddrBookType. Элементы структуры объявлены с модификаторами доступа public, так как по умолчанию элементы доступны только внутри структуры */
class Class1
{
static void Main(string[] args)
{ // Создание массива структур
AddrBookType[] myAB= new AddrBookType[400];
char icount;
char iloop;
int i=0;
int j=0;
while (true) // Запрос кода операции
{Console.Write ("Insert kod (0 - new record,
1 - show all, 2 - exit): ");
icount = (char)Console.Read ();
while (true) // Чтение потока ввода
{ iloop = (char)Console.Read ();
/* Цикл чтения символов будет завершен при
нажатии пользователем клавиши Enter и
получения из потока ввода символа '\n' */
if (iloop == '\n') break;}
if (icount=='2') break;
switch (icount)
{case '0':
Console.WriteLine("Insert Name: ");
myAB[i].name =Console.ReadLine ();
Console.WriteLine("Insert phone : ");
myAB[i].telfax= Console.ReadLine ();
i++; // Счетчик введенных элементов
break;
case '1': // Запись в стандартный поток
// вывода
for ( j=0; j
{Console.Write(myAB[j].name);
Console.Write(" ");
Console.WriteLine(myAB[j].telfax); }
break;
default :
Console.WriteLine("Ошибка ввода");
break;
} // Конец switch
} // Конец while
}
} }
Явный вызов конструктора
Определение конструктора может содержать явный вызов конструктора того же класса. Вызываемый конструктор указывается после имени определяемого конструктора со списком параметров через символ двоеточия. Вызываемый конструктор может быть определен ключевым словом this - для вызова конструктора из того же самого класса, или ключевым словом base - для вызова конструктора базового класса. Явно вызываемый конструктор будет выполнен до выполнения конструктора, в котором он указывается.
Например:
public class A { public A():this(222) // Конструктор без параметров { } public A(int i) // Конструктор с одним параметром { } }
Комментарии в программе на языке C#
Комментарий в языке С# может быть как однострочным, так и многострочным.
Однострочный комментарий может размещаться в начале строки или после некоторого кода. Он начинается символами // и завершается концом строки.
Многострочный комментарий располагается между парами символов /* и */ .
Комментарий, вставляемый средой проектирования, например // TODO: Add code to start application here указывает место, в которое должен быть вставлен код, выполняемый при запуске приложения.
Существует особый тип комментария, который записывается в summary-секции:
/// <summary> - /// </summary>.
Такой комментарий может быть использован для автоматического документирования приложения.
При автоматическом документировании приложения создается XML-файл, представляющий собой информацию о приложении как некоторую иерархию секций. Для того чтобы при компиляции приложения создавался XML-файл документа, следует установить опции компиляции следующим образом: в окне Solution Explorer выделить секцию с именем проекта и выполнить команду меню View|Property Pages (или Shift+F4), а затем, выбрав папку Configuration Properties и страницу свойств Build, установить новое значение свойства XML Documentation File, описывающее имя файла, в котором будет сохранен XML-документ.
Методы члены класса
Среда проектирования Visual Studio .NET дает возможность использовать мастер создания метода - члена класса (в окне Class View выбрать имя класса и выполнить команду контекстного меню Add|Add Metod). Список Modifier. диалога. C# Metod Wizard позволяет указать один из следующих модификаторов параметра метода:
none - определяет передачу параметров по значению. Если внутри метода будет изменено значение фактического параметра, то вне метода его значение останется прежним;ref - определяет передачу параметров по ссылке. Изменение параметра внутри метода останется и после завершения метода. ref-параметр перед использованием обязательно должен быть инициализирован;out - определяет передачу параметров по результату. При завершении метода конечное значение формального параметра присваивается указанному при вызове фактическому параметру. При этом в момент вызова метода фактический параметр может не быть инициализирован.
Например:
public void Metod1(int i, ref int j, out int k) { }
При обращении к методу или полю - членам класса используется операция . (точка) - доступ к члену класса. Имя поля или метода члена класса квалифицируется именем экземпляра класса.
Язык C# позволяет использовать методы с переменным числом параметров. Для метода с переменным числом параметров должны быть выполнены следующие правила:
переменный список параметров выступает как единый параметр и указывается ключевым словом params;кроме переменного списка параметров, никаких других параметров после ключевого слова params в методе указывать нельзя;в методе может быть указано только одно ключевое слово params, определяющее переменный список параметров.
Количество параметров в переменном списке параметров определяется свойством Length .
Например:
using System; public class MyClass1 { public static void UseParams1(params int[] list) { // Отображение списка параметров for ( int i = 0 ; i < list.Length ; i++ ) Console.WriteLine(list[i]); } public static void UseParams2(params object[] list) { // В переменном списке параметров могут быть // объекты различных типов for ( int i = 0 ; i < list.Length ; i++ ) Console.WriteLine((object)list[i]); } public static void UseParams3(int k,params object[] list) { // В переменный список параметров // включаются параметры, начиная // со второго for ( int i = 0 ; i < list.Length ; i++ ) Console.WriteLine((object)list[i]); } public static void Main() { UseParams1(1, 2, 3, 4, 5); UseParams1(1, 2); int[] myarray = new int[3] {1,2,3}; UseParams1(myarray); UseParams2(111, 'f', "string"); UseParams3(111, 'f', "string");
} }
Листинг 15.2.
Модификаторы доступа
В языке C# применяются следующие модификаторы доступа:
public - доступ не ограничен;protected - доступ ограничен только наследуемыми классами;internal - доступ ограничен рамками текущего проекта;private - доступ ограничен рамками данного класса.
Для любого члена класса или объектного типа разрешено указывать только один модификатор доступа, за исключением комбинации protected internal, регламентирующей ограничение доступа наследуемыми классами текущего проекта.
Например:
class A { protected int x = 100; }
class B : A { void M1() { A a1 = new A(); // Создание объекта типа A B b1 = new B(); // Создание объекта типа B // a1.x = 200; - доступ не разрешен b1.x = 200; // Правильно реализованный доступ } }
Отметим, что пространство имен не может иметь модификатора доступа.
Язык C# поддерживает использование вложенных классов.
Типы верхнего уровня, которые не являются вложенными в другие типы, могут иметь модификатор доступа только internal (по умолчанию) или public.
Если модификатор доступа не указан, то применяется доступ по умолчанию. В следующей таблице отображены модификаторы доступа для вложенных типов, являющихся членами других типов.
enum | Public | - | |
class | private | public protected internal private protected internal | |
interface | public | - | |
struct | private | public internal private
Структуры не могут иметь модификатор доступа protected,так как не могут быть наследуемы |
Например:
using System; namespace MyNameSpace { public class A { public static int myPublicInt; internal static int myInternalInt; private static int myPrivateInt = 0;
public class Nest1 // "Вложенный" член класса { public static int myPublicInt; internal static int myInternalInt; private static int myPrivateInt = 0; }
private class Nest2 // "Вложенный" член класса { public static int myPublicInt = 0; internal static int myInternalInt = 0; private static int myPrivateInt = 0; } } public class MyClass { public static int Main() { // Доступ к членам класса A: A.myPublicInt = 1; // Доступ не ограничен A.myInternalInt = 2; // Только в текущем проекте // A.myPrivateInt = 3; - ошибка: нет // доступа вне класса // Доступ к членам класса Nest1: A.Nest1.myPublicInt = 1; // Доступ не ограничен A.Nest1.myInternalInt = 2; // Только в текущем проекте // A.Nest1.myPrivateInt = 3; - ошибка: нет // доступа вне класса Nest1 // Доступ к членам класса Nest2: // A.Nest2.myPublicInt = 1; - ошибка: нет // доступа вне класса A // A.Nest2.myInternalInt = 2; - ошибка: нет // доступа вне класса A // A.Nest2.myPrivateInt = 3; - ошибка: нет // доступа вне класса Nest2 return 0; } } }
Листинг 15.1.
Объявление класса
В языке С# определение класса не обязательно должно иметь методы конструктор и деструктор.
Управляемый код на языке С# избавлен от необходимости освобождения памяти, выделяемой под объекты, так как это реализуется средой NET Framework. Поэтому основное назначение деструктора в языке C# - это освобождение неуправляемых ресурсов, таких как окна, файлы, сетевые соединения и т.п.
Язык C# поддерживает три типа конструкторов:
конструктор экземпляра объекта ( instance), используемый при создании объекта;private-конструктор, указываемый в коде для предотвращения автоматического создания конструктора по умолчанию. Такой тип конструктора используется для классов, имеющих только статические члены. Экземпляр объекта с private-конструктором не может быть создан.статический конструктор ( static), вызываемый для инициализации класса до создания первого объекта или до первого вызова статического метода. Статический конструктор не может иметь модификаторы доступа и список параметров.
Конструктор экземпляра объекта имеет следующее формальное описание:
[атрибуты] [модификаторы_доступа] имя_конструктора([список_формальных_параметров]) [:base (список_аргументов) | :this (список_аргументов)] { тело_конструктора }
Ключевое слово base определяет явный вызов конструктора базового класса, а ключевое слово this - вызов конструктора данного класса с указанным списком параметров.
Например:
public class AClass1 { public AClass1()// Объявление конструктора { } }
Ключевое слово class определяет имя объявляемого класса. Тело объявляемого класса указывается в фигурных скобках.
Ключевое слово public - это модификатор доступа, указывающий, что объявляемые после него идентификаторы (имена классов или методов) будут общедоступны (модификатор доступа позволяет определить область видимости переменных и методов - членов класса).
По умолчанию все переменные и методы - члены класса, заданные без модификатора доступа, считаются private-переменными (называемыми иногда приватными или закрытыми). Приватные переменные доступны только внутри экземпляра класса и не могут быть использованы во внешних функциях модуля.
Пространство имен
Пространство имен позволяет именовать группу данных, таких как классы, переменные и/или методы. В языке C# все библиотеки классов подключаются как пространства имен.
При автоматическом формировании проекта в среде Visual Studio.NET первой строкой создаваемого приложения вставляется строка using System.
Ключевое слово using подключает библиотеку классов System (каждая библиотека классов рассматривается как пространство имен).
Создание пространства имен указывается ключевым словом namespace.
Объявляемые пространства имен могут использоваться для структурирования программы.
Например:
namespace NameSN1.NameSN2 { class A {} } namespace NameSN3 { using NameSN1.NameSN2; class B: A {} }
В среде проектирования Visual Studio.NET библиотеки классов NET Framework образуют иерархическую структуру пространств имен.
Библиотеку классов среды .NET Framework иногда называют NET Framework-библиотекой или просто Framework-библиотекой.
Объявление пространства имен имеет следующее формальное описание:
namespace name[.name1] ...] { // объявляемые_данные }
Пространство имен указывается идентификатором, который может содержать операцию . , определяющую составное имя пространства имен.
Объявляемыми данными пространства имен могут быть:
другие пространства имен;классы; интерфейсы; структуры; перечисления.
Для того чтобы иметь возможность обращаться к переменным или методам из пространства имен, можно использовать один из следующих способов:
имя соответствующей переменной или метода должно быть квалифицировано названием пространства имен (пространство имен указывается перед именем через точку).
Например:
System.Console.WriteLine("Печать строки"); имя библиотеки должно быть установлено как доступное оператором using.
Например:
using System;
Директива using может использоваться для:
подключения пространства имен. Класс не может быть подключен директивой using; создания псевдонима имени класса. Псевдоним используется в программе для квалификации членов данного класса.
Объявление псевдонима имеет следующее формальное описание:
using alias=class_name;
Например:
using System.Console = my_SN; class MyClass { public static void Main() { my_SN.WriteLine("123");} }
Директива using позволяет не квалифицировать каждую переменную пространством имен, а просто подключить требуемое пространство имен.
Пространство имен System
Библиотека классов .NET Framework среды Visual Studio.NET состоит из иерархически организованного набора пространства имен. В каждом пространстве имен определяется набор типов (классы, структуры, нумераторы, интерфейсы). Пространство имен System содержит набор классов для общеиспользуемых значений и ссылочных типов данных, событий и обработчиков событий, интерфейсов, атрибутов и т.п. Также это пространство имен содержит классы, позволяющие выполнять преобразование типов, реализовывать операции ввода/вывода и т.п.
Все встроенные типы данных языка C# реализованы как классы пространства имен. Пространство имен System включает такие классы, как Console, String, Array, Math, Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Voidи т.п.
Псевдонимы типов языка C# и соответствующие им предопределенные типы пространства имен являются при написании программы взаимозаменяемыми.
Для определения типа переменной можно использовать метод GetType или оператор typeof.
Библиотека классов NET Framework предоставляет для реализации потоков ввода, вывода и ошибок класс Console, располагаемый в пространстве имен System.
Класс Console имеет следующие свойства, описывающие соответствующие потоки ввода/вывода:
In - стандартный поток ввода;Out - стандартный поток вывода;Error - стандартный поток вывода ошибок.
Класс Console содержит следующие методы, позволяющие осуществлять чтение и запись символов из потоков ввода/вывода:
Read - чтение символов из потока ввода;ReadLine - чтение строки символов из потока ввода;Write - запись строки символов в поток вывода;WriteLine - запись в поток вывода строки символа, ограниченной символами конца строки.
Работа с различными видами коллекций реализуется такими классами пространства имен System.Collections , как:
ArrayList - динамически расширяемый массив; BitArray - структура данных, каждый элемент которой реализуется как битовое значение; Hashtable - коллекция связанных ключей и значений; SortedList - массив, состоящий из пар "ключ-значение"; Queue - очередь; Stack - коллекция объектов, реализуемая как стек.
Создание экземпляра класса
Для использования переменных или методов класса следует создать объект - экземпляр класса.
В языке C# экземпляр класса всегда создается при помощи оператора new. Если класс имеет несколько конструкторов, то при создании переменной указывается требуемый конструктор.
Место выделения памяти под объект зависит от типа создаваемого объекта: объекты ссылочных типов размещаются в куче, а объекты размерных типов - в стеке.
Объявление переменной объектного типа в языке С# не создает объекта, а только определяет идентификатор указанного типа. Обратите внимание, что во многих объектно-ориентированных языках, таких как С++, объявление переменной объектного типа также становится и созданием экземпляра данного типа.
Например:
using System; namespace MyNS { public class A { public A() // Конструктор без параметров { Console.WriteLine("A()"); } public A(int i) // Конструктор с одним параметром { Console.Write("A(i) i= "); Console.WriteLine(i); } } } using System; namespace MyNS { class MyClass { static void Main(string[] args) { A acl1= new A(); // Создание экземпляра класса A acl2= new A(987); } } }
Структура приложения на языке С#
Проектом называется совокупность файлов, содержащих информацию об установках, конфигурации, ресурсах проекта, а также файлов исходного кода и заголовочных файлов.
Интегрированная среда проектирования VisualStudio.NET позволяет для создания проектов на разных языках программирования использовать различные инструментальные средства проектирования (например, Microsoft Visual Basic, Microsoft Visual C#).
Любое приложение на языке C#, разрабатываемое в среде проектирования VisualStudio.NET, реализуется как отдельный проект. Приложение на языке С# может состоять из нескольких модулей. Каждый модуль C# может содержать код нескольких классов (при создании приложения в среде Visual Studio.NET каждый класс C# автоматически помещается в отдельный модудь - файл с расщирением cs). Среда Visual Studio 2005 позволяет создавать частичные классы ( partial class), когда один класс содержится в нескольких файлах. Соединение всех частей класса выполняется на этапе компиляции. (Вообще, частичными также могут быть структуры и интерфейсы.)
Для консольного приложения один из классов, реализуемых модулем, должен содержать метод Main. В языке C# нет аппарата заголовочных файлов, используемого в языке С++, поэтому код модуля должен содержать как объявление, так и реализацию класса.
По умолчанию весь код класса, представляющего консольное приложение, заключается в одно пространство имен, одноименное с именем приложения.
Точкой входа в программу на языке C# является метод Main.
Этот метод может записываться как без параметров, так и с одним параметром типа string. - указателем на массив строк, который содержит значения параметров, введенных при запуске программы. В отличие от списка параметров, задаваемых при запуске С-приложения, список параметров C#-приложения не содержит в качестве первого параметра имя самого приложения.
Код метода указывается внутри фигурных скобок:
static void Main(string[] args) { }
Ключевое слово static определяет, что метод Main является статическим методом, вызываемым без создания экземпляра объекта типа класса, в котором этот метод определен.
Метод, не возвращающий никакого значения, указывается с ключевым словом void. Однако, метод Main может возвращать значение типа int.
Например:
static int Main(string[] args) { // Проверка числа введенных параметров if (args.Length == 0) { Console.WriteLine("Нет параметров"); return 1; } // Для получения значения параметра как значения типа long // используется функция Parse long num = Int64.Parse(args[0]); // Тип long языка C# является псевдонимом типа Int64. // Поэтому предыдущая запись эквивалентна записи // long num = long.Parse(args[0]); // Для получения значения параметра как значения // определенного типа также можно использовать // метод ToInt64 класса Convert long num = Convert.ToInt64(s); }
Компилятор C# допускает наличие метода Main сразу в нескольких классах. Но для успешной компиляции и выполнения такого приложения следует указать класс, метод Main которого следует считать точкой входа в приложение. Это можно сделать, установив опции проекта или указав опцию компилятора /main с именем класса, чей метод Main будет задействован (например: csc class1.cs class2.cs /main:Class2).
Для того чтобы установить опцию проекта, определяющую "стартовый" класс (Startup Object), следует открыть диалог свойств проекта Property Pages, в секции Common Properties на странице General выбрать опцию Startup Object и указать для нее имя "стартового" класса (рис. 1).
Рис. 15.1. Определение имени "стартового класса"
Структуры
Структуры языка C# идентичны классам и представляют поименованную совокупность компонент, называемых членами или элементами структуры. Элементом структуры может быть переменная любого допустимого типа или функция.
Однако структура относится к размерному типу, а класс - к ссылочному типу. Под структуру выделяется память, достаточная для размещения всех элементов структуры.
При создании массивов применение типа структуры бывает более эффективно, чем применение типа класса.
Тип struct является размерным типом, который может содержать конструкторы, константы, поля, методы, свойства, индексаторы, операторы и вложенные типы.
Объявление структуры может иметь следующее формальное описание (необязательные элементы указаны в квадратных скобках):
[атрибуты] ]модификаторы] struct имя_структуры [:интерфейсы] { конструктор () {} тип_элемента_структуры имя_ элемента1; тип_элемента_структуры имя_ элемента2; ... тип_элемента_структуры имя_ элементаN; } [ ;] // имя_элемента может быть именем переменной или именем функции
Для обращения к отдельным элементам структуры используется оператор . (точка).
Доступ к элементам структуры может иметь следующее формальное описание:
struct имя_структуры { модификатор тип_элемента элемент_структуры1; модификатор тип_элемента элемент_структуры2; } // Объявление переменной типа структуры имя_структуры имя_переменной_структуры1; имя_структуры [] имя_переменной_структуры2 = new имя_структуры[размерность]; // Доступ к элементу структуры имя_переменной_структуры1.элемент_структуры1= значение1; имя_переменной_структуры2[индекс].элемент_структуры1=значение2;
Например:
/* Данный пример иллюстрирует консольное приложение, позволяющее добавлять и отображать элементы структуры. Используемый массив myAB содержит 400 элементов, каждый из которых является структурой из двух полей - name и telfax*/ using System; namespace MyStruct1 { struct AddrBookType { public string name; public string telfax ; } /* Созданная структура определяет новый структурный тип AddrBookType. Элементы структуры объявлены с модификаторами доступа public, так как по умолчанию элементы доступны только внутри структуры */ class Class1 { static void Main(string[] args) { // Создание массива структур AddrBookType[] myAB= new AddrBookType[400]; char icount; char iloop; int i=0; int j=0; while (true) // Запрос кода операции {Console.Write ("Insert kod (0 - new record, 1 - show all, 2 - exit): "); icount = (char)Console.Read (); while (true) // Чтение потока ввода { iloop = (char)Console.Read (); /* Цикл чтения символов будет завершен при нажатии пользователем клавиши Enter и получения из потока ввода символа '\n' */ if (iloop == '\n') break;} if (icount=='2') break; switch (icount) {case '0': Console.WriteLine("Insert Name: "); myAB[i].name =Console.ReadLine (); Console.WriteLine("Insert phone : "); myAB[i].telfax= Console.ReadLine (); i++; // Счетчик введенных элементов break; case '1': // Запись в стандартный поток // вывода for ( j=0; j<i;j++) {Console.Write(myAB[j].name); Console.Write(" "); Console.WriteLine(myAB[j].telfax); } break; default : Console.WriteLine("Ошибка ввода"); break; } // Конец switch } // Конец while } } }
Листинг 15.3.
Язык C# позволяет создавать переменные типы структуры двумя способами:
традиционным способом с применением оператора new- в этом случае объявление переменной типа структуры идентично объявлению переменной типа класса и при объявлении переменной возможна одновременная инициализация полей структуры;обычным объявлением переменной как для любого размерного типа.
Управляемый код
Язык программирования C# был разработан как язык, использующий технологию .NET. Поэтому приложение на C# компилируется в промежуточный код, называемый IL-кодом (Intermediate Language) или MSIL-кодом (Microsoft intermediate language). Такой код перед выполнением компилируется JIT-компилятором в команды, отвечающие специфике конкретного процессора.
Выполнение IL-кода управляется механизмом CLR (Common Language Runtime), непосредственно осуществляющим JIT-компиляцию (Just In Time), наложение политик безопасности, предоставление отладочных сервисов, автоматическое управление памятью (реализация механизма "сборки мусора"). IL-код можно компилировать как при установке приложения, так и при его выполнении (при этом компилироваться будет не весь код, а только реально вызываемые методы). В процессе компиляции кроме IL-кода формируются также метаданные, описывающие классы. CLR использует метаданные для поиска и загрузки классов, размещения объектов в памяти, определения входящих в класс методов и свойств. CLR можно рассматривать как некоторую виртуальную машину, выполняющую приложения .NET. Среда CLR обеспечивает единообразное поведение всех приложений вне зависимости от языка реализации кода. CLS-спецификация (Common Language Specification) определяет требования к CLS-совместимым языкам программирования, использующим классы .NET Framework как некоторую унифицированную систему типов.
Кроме создания MSIL-кода, CLS-совместимый компилятор добавляет в выходной EXE-файл метаданные, описывающие используемые типы и методы. Под метаданными понимаются некоторые данные, которые описывают другие данные. Используя метаданные, среда CLR определяет требуемые во время выполнения типы и методы.
Библиотеки классов .NET Framework предоставляют большой набор методов отражения, применяемых средой CLS и другими приложениями для получения информации о типах и методах, реализуемых приложением. Отражением называется механизм получения метаданных. АPI отражения среды .NET представляет собой иерархию классов System.Reflection.
.NET Framework - это та часть платформы .NET, которая предназначена для создания надежных, масштабируемых и распределенных приложений, использующих технологии .NET.
.NET Framework состоит из CLR (Common Language Runtime) - среды времени выполнения кода, и из набора библиотек классов (иногда называемых BCL - Base Class Library).
Приложения, выполняемые под управлением CLR, называются управляемым кодом.
В Windows выполняемым приложением является EXE-файл, библиотеки реализуются как DLL-файлы. Технология .NET предоставляет приложения в виде сборок, описывающих "логические" EXE- или DLL- файлы. Сборка содержит декларацию, которая описывает ее содержимое. Так, сборка mscorlib.dll содержит все стандартные пространства имен из пространства имен System для .NET.
Абстрактные классы
Абстрактным классом называется класс, который содержит один или несколько абстрактных методов.
Абстрактный класс не может использоваться для создания объектов.
Как правило, абстрактный класс описывает некий интерфейс, который должен быть реализован всеми его производными классами.
Абстрактный метод языка C# не имеет тела метода и аналогичен чисто виртуальному методу языка C++.
Например:
public abstract int M1(int a, int b);
Абстрактный класс можно использовать только как базовый для других классов. При этом если производный класс не содержит реализации абстрактного метода, то он также является абстрактным классом.
По умолчанию при создании абстрактного класса в среде VisualStudio .NET в формируемый абстрактный класс автоматически вставляется только один метод - конструктор без параметров.
и тип ссылки совпадают CA
using System; namespace MyDerriv1 { class Class1 { static void Main(string[] args) { // Тип объекта и тип ссылки совпадают CA var1; var1=new CA(); // Вызывается метод класса CA Console.WriteLine (var1.F1()); // Вызывается метод класса CA Console.WriteLine (var1.F2()); // Тип объекта - CB , а тип ссылки - CA CA var2; var2=new CB(); // Вызывается метод класса CA Console.WriteLine (var2.F1()); // Вызывается метод класса CB Console.WriteLine (var2.F2()); } } } // Класс CA using System; namespace MyDerriv1 { public class CA { public CA() { } public int F1() { return 1; } public virtual string F2() {return "Метод F2 класса CA";} } } // Класс CB using System; namespace MyDerriv1 { public class CB : MyDerriv1.CA { public CB() { } public int F1() {return 2; } // Переопределение виртуального метода F2 public override string F2() { return "Метод F2 класса CB"; } } } |
Листинг 16.1. |
Закрыть окно |
using System;
namespace MyDerriv1
{
class Class1
{
static void Main(string[] args)
{
// Тип объекта и тип ссылки совпадают
CA var1; var1=new CA();
// Вызывается метод класса CA
Console.WriteLine (var1.F1());
// Вызывается метод класса CA
Console.WriteLine (var1.F2());
// Тип объекта - CB , а тип ссылки - CA
CA var2; var2=new CB();
// Вызывается метод класса CA
Console.WriteLine (var2.F1());
// Вызывается метод класса CB
Console.WriteLine (var2.F2());
}
}
}
// Класс CA
using System;
namespace MyDerriv1
{
public class CA
{ public CA() { }
public int F1() { return 1; }
public virtual string F2()
{return "Метод F2 класса CA";}
}
}
// Класс CB
using System;
namespace MyDerriv1
{ public class CB : MyDerriv1.CA
{
public CB() { }
public int F1() {return 2; }
// Переопределение виртуального метода F2
public override string F2()
{ return "Метод F2 класса CB"; }
}
}
определяющий однопотоковую модель static void
using System; namespace MyAClass1 { class Class1 {[STAThread] // Класс // System.STAThreadAttribute, // определяющий однопотоковую модель static void Main(string[] args) { CB varb1 = new CB(); /* Запрос о совместимости типа объекта с типом наследуемого им интерфейса */ if (varb1 is IA) /* Если переменная типа CB является переменной типа класса, реализующего запрашиваемый интерфейс IA, то оператор is вернет значение true. */ {Console.WriteLine ("varb1 - это ссылка на класс, который реализует интерфейс IA");} // Создание объекта типа интерфейса IA IA ivar=(IA)varb1; if (ivar is IA) {Console.WriteLine ("ivar - это ссылка на интерфейс IA");} bool var1=ivar.F1(); Console.WriteLine("Вызов метода F1 интерфейса IA: {0}",var1); // Приведение объекта к типу интерфейса Object ivar1 = varb1 as IA; if (ivar1 != null) Console.WriteLine ( "ivar1 - это ссылка на интерфейс IA"); } } } using System; namespace MyAClass1 { public class CB : MyAClass1.CA,IA {public CB() { } public bool F1() { return true; } public int F2(int a) { return a*10; } } interface IA { bool F1();} } using System; namespace MyAClass1 { public abstract class CA { public CA() { } public abstract int F2(int a); } } |
Листинг 16.2. |
Закрыть окно |
using System;
namespace MyAClass1
{ class Class1
{[STAThread] // Класс
// System.STAThreadAttribute,
// определяющий однопотоковую модель
static void Main(string[] args)
{
CB varb1 = new CB();
/* Запрос о совместимости типа объекта с типом наследуемого им интерфейса */
if (varb1 is IA)
/* Если переменная типа CB является переменной типа класса, реализующего запрашиваемый интерфейс IA, то оператор is вернет значение true. */
{Console.WriteLine ("varb1 - это ссылка на
класс, который реализует интерфейс IA");}
// Создание объекта типа интерфейса IA
IA ivar=(IA)varb1;
if (ivar is IA)
{Console.WriteLine ("ivar - это ссылка на интерфейс IA");}
bool var1=ivar.F1();
Console.WriteLine("Вызов метода F1 интерфейса IA: {0}",var1);
// Приведение объекта к типу интерфейса
Object ivar1 = varb1 as IA;
if (ivar1 != null)
Console.WriteLine ( "ivar1 - это ссылка на
интерфейс IA");
}
}
}
using System;
namespace MyAClass1
{
public class CB : MyAClass1.CA,IA
{public CB() { }
public bool F1() { return true; }
public int F2(int a) { return a*10; }
}
interface IA { bool F1();}
}
using System;
namespace MyAClass1
{ public abstract class CA
{ public CA() { }
public abstract int F2(int a);
}
}
Механизмы наследования
Объявление класса в языке C# создает новый ссылочный тип, определяющий как описание методов, так и их реализацию.
Объявление интерфейса создает новый ссылочный тип, который специфицирует описание методов и имена некоторых констант, но не определяет саму реализацию.
Интерфейс может быть объявлен для расширения одного или нескольких интерфейсов.
Наследование позволяет определять новые классы в терминах существующих классов. В языке C# поддерживается только простое наследование: любой подкласс является производным только от одного непосредственного суперкласса. При этом любой класс может наследоваться от нескольких интерфейсов.
Методы - члены класса
В среде VisualStudio.NET добавить в класс новый метод можно, используя контекстное меню окна Class View. На рис. 16.2 приведен диалог C# MetodWizard, позволяющий определить модификаторы метода и список формальных параметров.
Рис. 16.2. Диалог C# MetodWizard
Язык C# поддерживает следующие модификаторы метода члена класса:
static - определяет статический метод, доступный без создания экземпляра класса;abstract - определяет абстрактный метод, который является членом абстрактного класса;virtual - метод, реализация которого может быть переопределена в производных классах;extern - метод, имеющий реализацию вне данного класса (внешний метод);override- метод, выполняющий переопределение виртуальной функции, наследуемой от базового класса;new - метод, скрывающий в производном классе наследуемый метод с тем же именем (если ключевое слово не указано, то имя скрывается, но при компиляции отображается предупреждение warning).
Порядок указания модификаторов доступа и модификаторов метода несущественен.
Виртуальные и абстрактные методы всегда должны указываться с модификатором доступа public.
Определение интерфейса
В языке C# отсутствует множественное наследование: каждый класс может иметь только один непосредственный базовый класс. Частичной заменой множественному наследованию может служить использование интерфейсов.
Интерфейсы могут содержать свойства, методы и индексаторы, но без их реализации.
Один класс языка C# может наследовать несколько интерфейсов.
В C# интерфейс определяет новый ссылочный тип, содержащий объявления методов, которые обязательно должны быть реализованы в классе, наследующем данный интерфейс.
Можно сказать, что интерфейс определяет некоторую модель поведения, которая должна быть реализована в любом классе, наследующем данный интерфейс.
Объявление интерфейса может иметь следующее формальное описание:
[атрибуты] [модификаторы] interface имя_интерфейса [:список_базовых_интерфейсов] {тело_интерфейса}[;]
Например:
interface IMyInterface: IBase1, IBase2 { int M1(); int M2(); }
Если класс, наследующий интерфейс, не является абстрактным, то он обязательно должен реализовать методы, объявленные в интерфейсе. При наследовании интерфейса абстрактным классом методы, объявленные в интерфейсе, могут не иметь реализации только в том случае, если они объявляются с модификатором abstract.
Например:
public abstract class CMyInterface : IMyInterface { public CMyInterface() { // } public abstract int M1(); public abstract int M2(); } interface IMyInterface { int M1(); int M2(); }
Определение типа объекта
Для получения информации о том, является ли тип объекта времени выполнения совместимым с заданным типом, используется оператор is. Если типы совместимы, то оператор возвращает значение true.
Оператор as аналогичен оператору is, с той лишь разницей, что выполняет приведение в случае совместимости типов или устанавливает ссылку на несовместимый объект равной null.
Применение оператора is имеет следующее формальное описание:
выражение is тип
Например:
string str1 = myObjects; if (str1 is string) Console.WriteLine ("тип string");
Применение оператора as имеет следующее формальное описание:
выражение as тип
Например:
string str1 = myObjects as string; if (str1 != null) Console.WriteLine ( "это строка" );
При этом предыдущая форма записи эквивалентна следующей записи:
выражение as тип ? (тип)выражение : (тип)null
Приведение типа объекта к типу интерфейса
При создании объект сразу можно приводить к типу интерфейса, используя оператор as
Например:
Object ivar1 = varb1 as IA;
Если приведение допустимо, то переменная будет содержать ссылку на интерфейс. Если приведение типа объекта типу интерфейса недопустимо, то в результате приведения типа переменная примет значение null.
Следующий пример иллюстрирует определение типов объектов и приведение типа объекта к типу интерфейса.
using System; namespace MyAClass1 { class Class1 {[STAThread] // Класс // System.STAThreadAttribute, // определяющий однопотоковую модель static void Main(string[] args) { CB varb1 = new CB(); /* Запрос о совместимости типа объекта с типом наследуемого им интерфейса */ if (varb1 is IA) /* Если переменная типа CB является переменной типа класса, реализующего запрашиваемый интерфейс IA, то оператор is вернет значение true. */ {Console.WriteLine ("varb1 - это ссылка на класс, который реализует интерфейс IA");} // Создание объекта типа интерфейса IA IA ivar=(IA)varb1; if (ivar is IA) {Console.WriteLine ("ivar - это ссылка на интерфейс IA");} bool var1=ivar.F1(); Console.WriteLine("Вызов метода F1 интерфейса IA: {0}",var1); // Приведение объекта к типу интерфейса Object ivar1 = varb1 as IA; if (ivar1 != null) Console.WriteLine ( "ivar1 - это ссылка на интерфейс IA"); } } } using System; namespace MyAClass1 { public class CB : MyAClass1.CA,IA {public CB() { } public bool F1() { return true; } public int F2(int a) { return a*10; } } interface IA { bool F1();} } using System; namespace MyAClass1 { public abstract class CA { public CA() { } public abstract int F2(int a); } }
Листинг 16.2.
Производные классы
В среде VisualStudio.NET новый производный класс можно создать, используя окно ClassView (выполнив в нем команду контекстного меню Add|Add Class). Рисунок 16.1 иллюстрирует страницы диалога C# Class Wizard, предлагаемого средой VisualStudio.NET для создания нового класса.
Рис. 16.1. Диалог C# Class Wizard
Имя создаваемого класса указывается в поле Class Name на странице Class Options диалога C# Class Wizard.
В поле Namespace указывается пространство имен, к которому будет принадлежать создаваемый класс. По умолчанию проект размещается в пространстве имен, одноименным с именем проекта.
В поле Access выбирается модификатор доступа для создаваемого класса.
Для класса в языке C# возможно использование двух модификаторов доступа:
public - определяет, что нет ограничений на использование класса;internal - определяет, что класс будет доступен для файлов, входящих в ту же сборку.
Сборка - это физический файл, который состоит из нескольких PE-файлов (portable executable), генерируемых компилятором среды .NET. В сборку входит декларация (manifest), содержащая описание сборки для управляющей среды .NET.
Класс может имеет один из следующих модификаторов класса:
abstract - определяет, что класс должен быть использован только как базовый класс других классов. Такие классы называются абстрактными классами;sealed - определяет, что класс нельзя использовать в качестве базового класса. Такие классы в языке C# иногда называются изолированными классами.
Очевидно, что изолированный класс не может быть одновременно и абстрактным классом.
Объявление класса может иметь следующее формальное описание:
МодификаторДоступа МодификаторКласса class ИмяКласса : ИмяНаследуемогоКласса {ТелоКласса }
Тело класса содержит описание переменных, методов и вложенных классов и заключается в фигурные скобки. В частном случае тело класса может не содержать ни одного объявления.
Например:
namespace CA1 { public abstract class Class2 : CA1.Class1 { public Class2() { // TODO: Add constructor logic here } } }
Виртуальные методы
Виртуальные методы объявляются в базовом классе с ключевым словом virtual, а в производном классе могут быть переопределены. Метод, который переопределяет виртуальный, указывается ключевым словом override. Прототипы виртуальных методов как в базовом, так и в производном классе должны быть одинаковы.
Применение виртуальных методов позволяет реализовывать механизм позднего связывания.
На этапе компиляции строится только таблица виртуальных методов, а конкретный адрес проставляется уже на этапе выполнения.
При вызове метода - члена класса действуют следующие правила:
для виртуального метода вызывается метод, соответствующий типу объекта, на который имеется ссылка;для невиртуального метода вызывается метод, соответствующий типу самой ссылки.
При позднем связывании определение вызываемого метода происходит на этапе выполнения (а не при компиляции) в зависимости от типа объекта, для которого вызывается виртуальный метод.
При раннем связывании определение вызываемого метода происходит на этапе компиляции.
Например:
using System; namespace MyDerriv1 { class Class1 { static void Main(string[] args) { // Тип объекта и тип ссылки совпадают CA var1; var1=new CA(); // Вызывается метод класса CA Console.WriteLine (var1.F1()); // Вызывается метод класса CA Console.WriteLine (var1.F2()); // Тип объекта - CB , а тип ссылки - CA CA var2; var2=new CB(); // Вызывается метод класса CA Console.WriteLine (var2.F1()); // Вызывается метод класса CB Console.WriteLine (var2.F2()); } } } // Класс CA using System; namespace MyDerriv1 { public class CA { public CA() { } public int F1() { return 1; } public virtual string F2() {return "Метод F2 класса CA";} } } // Класс CB using System; namespace MyDerriv1 { public class CB : MyDerriv1.CA { public CB() { } public int F1() {return 2; } // Переопределение виртуального метода F2 public override string F2() { return "Метод F2 класса CB"; } } }
Листинг 16.1.
Вложенные классы
Язык C# позволяет создавать вложенные классы. Вложенный класс объявляется как член другого класса.
Права доступа для вложенного класса могут быть или меньшими, или такими же, как у содержащего его класса. Так, вложенный класс не может быть общедоступным, если содержащий его класс объявлен как internal.
При доступе к имени вложенного класса из членов внешнего класса квалификация именем внешнего класса не требуется.
Например:
using System; namespace MyNClass1 {class Class1 { static void Main(string[] args) {Class1.Class2 var1= new Class1.Class2(); // Эквивалентно // записи: Class2 var1= new Class2(); //Вызов метода вложенного класса Console.WriteLine( var1.F1()); } class Class2 { private int a=12345; public int F1() {return this.a;} } } }
Атрибуты
Язык С# позволяет создавать атрибуты для различных элементов языка, таких как типы, методы, поля и свойства классов. Данные, хранимые в атрибутах, можно запрашивать во время выполнения приложения. Атрибуты - это механизм, позволяющий создавать самоописывающиеся приложения.
Использование атрибутов позволяет получать метаданные периода выполнения.Каждый атрибут - это экземпляр класса, производного от System.Attribute.
Назначаемый типу или члену класса атрибут указывается в квадратных скобках перед типом или членом класса.
Про атрибут, указанный для класса, иногда говорят, что этот атрибут "прикреплен к целевому типу".
Класс Attribute пространства имен System предоставляет следующие члены класса:
GetType - получает объект типа Type текущего экземпляра;ToString - возвращает строку, описывающую данный объект;IsDefined - определяет, существует ли атрибуты заданного типа, назначенные указываемому члену класса;GetCustomAttribute - запрашивает атрибут заданного типа для указанного члена класса.
Для класса Attribute определено свойство TypeId, определяющее уникальный идентификатор атрибута.
Доступ к атрибуту
Значения атрибутов или их существование могут быть запрошены во время выполнения приложения.
При запросе для класса или для члена класса данных о прикрепленных к нему атрибутах применяется отражение. Отражением называется функция, используемая во время выполнения приложения для получения метаданных, в том числе и заданных атрибутами.
Для реализации отражения библиотека NET Framework предоставляет несколько классов, базовым для которых служит класс отражения System.Reflection.
Основные методы отражения, используемые для запроса атрибутов, предоставляются классами System.Reflection.MemberInfo и System.Reflection.Assembly. Так, метод GetCustomAttributes позволяет определить атрибуты, присущие данному объекту.
Например:
class MyClass { public static void Main() { System.Reflection.MemberInfo info = typeof(MyClass); object[] attr = info.GetCustomAttributes(true); for (int i = 0; i < attr.Length; i ++) { System.Console.WriteLine(attr[i]); } } }
В результате выполнения данного кода в массив будут помещены классы всех назначенных атрибутов, а также класс System.Reflection.DefaultMemberAttribute. Доступ к типу объекта может быть реализован посредством класса Type . Метод Object.GetType возвращает объект типа Type , представляющий тип экземпляра объекта. Объект типа Type представляет собой ассоциированный с типом объект, используемый для доступа к метаданным типа.
Используя класс Type, можно создать объект для типа, экземпляр которого еще не был создан.
Например:
Type t= typeof(MyClass1);
Для создания объекта типа, ассоциированного с типом существующего экземпляра объекта, следует использовать метод GetType.
Например:
Type t = obj1.GetType(); , где obj1 экземпляр класса MyClass1.
При использовании объекта типа атрибута метод GetCustomAttribute возвращает значения атрибута, который назначен указанному параметрами объекту: первый параметр метода определяет ассоциированный объект, а второй - указывает класс атрибута.
Например:
MyAttribute MyAttr = (MyAttribute) Attribute.GetCustomAttribute( t, typeof(MyAttribute));
Следующий пример иллюстрирует доступ к значениям атрибутов.
class Class1 { static void Main(string[] args) { // Создание объекта, ассоциированного с типом AClass1 Type t= typeof(AClass1); MyAttribute MyAttr = (MyAttribute) Attribute.GetCustomAttribute( t, typeof (MyAttribute)); if(null == MyAttr) {Console.WriteLine("Атрибут не найден");} else { Console.WriteLine("Атрибут name = {0}, атрибут kod = {1}" , MyAttr.Name, // Возвращает значение // поля Name атрибута MyAttribute MyAttr.Kod); } } } [AttributeUsage(AttributeTargets.All)] public class MyAttribute : System.Attribute { private string name; private int kod; public MyAttribute(string name) {this.name = name; this.kod = 10; } public string Name { get { return name;} } public int Kod { get { return kod; } set {kod=value; } } }
[My("NameOfClass", Kod=123)] public class AClass1 { public AClass1() { } private int [] iArr = new int[10]; public int this[int ind1] { get { return iArr[ind1];} set { iArr[ind1]= value; } } }
Листинг 17.1.
с типом AClass1 Type t=
class Class1 { static void Main(string[] args) { // Создание объекта, ассоциированного с типом AClass1 Type t= typeof(AClass1); MyAttribute MyAttr = (MyAttribute) Attribute.GetCustomAttribute( t, typeof (MyAttribute)); if(null == MyAttr) {Console.WriteLine("Атрибут не найден");} else { Console.WriteLine("Атрибут name = {0}, атрибут kod = {1}" , MyAttr.Name, // Возвращает значение // поля Name атрибута MyAttribute MyAttr.Kod); } } } [AttributeUsage(AttributeTargets.All)] public class MyAttribute : System.Attribute { private string name; private int kod; public MyAttribute(string name) {this.name = name; this.kod = 10; } public string Name { get { return name;} } public int Kod { get { return kod; } set {kod=value; } } } [My("NameOfClass", Kod=123)] public class AClass1 { public AClass1() { } private int [] iArr = new int[10]; public int this[int ind1] { get { return iArr[ind1];} set { iArr[ind1]= value; } } } |
Листинг 17.1. |
Закрыть окно |
class Class1
{ static void Main(string[] args)
{
// Создание объекта, ассоциированного с типом AClass1
Type t= typeof(AClass1);
MyAttribute MyAttr =
(MyAttribute) Attribute.GetCustomAttribute(
t,
typeof (MyAttribute));
if(null == MyAttr)
{Console.WriteLine("Атрибут не найден");}
else
{
Console.WriteLine("Атрибут name = {0},
атрибут kod = {1}" ,
MyAttr.Name, // Возвращает значение
// поля Name атрибута MyAttribute
MyAttr.Kod); }
}
}
[AttributeUsage(AttributeTargets.All)]
public class MyAttribute : System.Attribute
{ private string name;
private int kod;
public MyAttribute(string name)
{this.name = name; this.kod = 10; }
public string Name { get { return name;} }
public int Kod { get { return kod; }
set {kod=value; } }
}
[My("NameOfClass", Kod=123)]
public class AClass1
{ public AClass1() { }
private int [] iArr = new int[10];
public int this[int ind1] {
get { return iArr[ind1];}
set { iArr[ind1]= value; } }
}
n Число методов public
public static void Main() { Type myType =(typeof(MyClass1)); // Получить методы с доступом public MethodInfo[] myArrMethodInfo = myType.GetMethods(BindingFlags.Public |BindingFlags.Instance |BindingFlags.DeclaredOnly); Console.WriteLine("\ n Число методов public =:" +myArrMethodInfo.Length); Console.WriteLine("Имена методов public : "); // Отобразить имена всех методов MyPrintMethodInfo(myArrMethodInfo); // Получить методы с защищенным доступом MethodInfo[] myArrMethodInfo1 = myType.GetMethods(BindingFlags.NonPublic |BindingFlags.Instance |BindingFlags.DeclaredOnly); Console.WriteLine("\n Число защищенных методов:" +myArrMethodInfo1.Length); } public static void MyPrintMethodInfo(MethodInfo[] a) { for(int i=0;i<a.Length;i++) { MethodInfo myMethodInfo = (MethodInfo)a[i]; Console.WriteLine("\n" + myMethodInfo.Name); } } |
Листинг 17.2. |
Закрыть окно |
public static void Main()
{
Type myType =(typeof(MyClass1));
// Получить методы с доступом public
MethodInfo[] myArrMethodInfo =
myType.GetMethods(BindingFlags.Public
|BindingFlags.Instance
|BindingFlags.DeclaredOnly);
Console.WriteLine("\ n Число методов public =:"
+myArrMethodInfo.Length);
Console.WriteLine("Имена методов public : ");
// Отобразить имена всех методов
MyPrintMethodInfo(myArrMethodInfo);
// Получить методы с защищенным доступом
MethodInfo[] myArrMethodInfo1 =
myType.GetMethods(BindingFlags.NonPublic
|BindingFlags.Instance
|BindingFlags.DeclaredOnly);
Console.WriteLine("\n Число защищенных методов:"
+myArrMethodInfo1.Length);
}
public static void MyPrintMethodInfo(MethodInfo[] a)
{
for(int i=0;i
{
MethodInfo myMethodInfo = (MethodInfo)a[i];
Console.WriteLine("\n" + myMethodInfo.Name);
}
}
Индексаторы на базе многомерных массивов
Для одного класса может быть создано несколько индексаторов. Индексаторы должны различаться числом параметров.
Например:
public class AClass1 { public AClass1() { } private int [] imyArray = new int[20]; public int this[int ind1] { get { return imyArray[ind1]; } set { imyArray[ind1]= value; } } private int [,] imyArray2 = new int[2,10]; public int this[int ind1, int ind2] { get {return imyArray2[ind1,ind2]; } set {imyArray2[ind1,ind2]= value; } } }
Используемость атрибута
Атрибуты могут быть использованы для различных элементов языка. Для того чтобы специфицировать, каким образом и для каких элементов можно использовать данный класс атрибута, библиотека NET Framework предоставляет класс System.AttributeUsageAttribute.
Спецификация используемости атрибута указывается в квадратных скобках перед именем определением класса.
Например:
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
Элементы языка, которым может быть назначен атрибут, указываются значением или набором значений перечислимого типа AttributeTargets.
Например, для использования данного атрибута только для классов или методов перед определением класса атрибута следует записать:
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
Спецификация используемости атрибута имеет следующее формальное описание:
[AttributeUsage( доступные_элементы, AllowMultiple=true_или_false, Inherited=наследуемость )]
Доступные элементы определяют те элементы языка, для которых может быть назначен данный атрибут. По умолчанию используется значение AttributeTargets.All (доступен для всех элементов).
Если именованный параметр AllowMultiple равен true, то классу или члену класса может быть назначено несколько атрибутов.
Параметр Inheritedопределяет, наследуется ли данный атрибут производным классом (по умолчанию - false).
Перечислимый тип AttributeTargets определяет следующее множество значений:
All - все элементы языка;Assembly - сборки;Class - классы;Constructor - конструкторы;Field - поля; Method - методы;Property - свойства;Delegate - делегаты;Enum - нумераторы;Event - события;Interface - интерфейсы;Module - модули;Parameter - параметры;ReturnValue - возвращаемые значения;Struct - структуры.
Элементы индексатора
Объект класса, используемого как аксессор, создается обычным образом. Инициализация элементов аксессора указывается как присвоение значений элементам массива. Доступ к элементам аксессора записывается как доступ к элементам массива.
Например:
AClass1 ac1= new AClass1(); ac1[0]=5; ac1[1]=6; Console.WriteLine("ac1[0]= {0}, ac1[1]= {1}", ac1[0],ac1[1]);
Класс Type
Класс System.Type имеет следующее формальное описание:
public abstract class Type : MemberInfo, IReflect.
Две ссылки типа Type на объект будут ссылаться на один и тот же объект только в том случае, если они представляют одинаковый тип.
Экземпляр типа Type может быть представлен любым из следующих типов:
классы;размерные типы (Value types);массивы;интерфейсы;указатели;нумераторы.
Ссылка на объект Type, ассоциируемый с типом, может быть получена одним из следующих способов:
метод Object.GetType возвращает объект Type, представляющий тип экземпляра объекта;статический метод GetType возвращает объект Type, который представляет тип, указанный посредством своего полного имени;методы Module.GetTypes, Module.GetType, и Module.FindTypes возвращают объект Type, который представляет типы, определенные в модуле. Метод GetTypes позволяет получить массив объектов для всех общедоступных и защищенных типов, определенных в модуле;метод FindInterfaces возвращает отфильтрованный список интерфейсов типов, которые поддерживаются данным типом;метод GetElementType возвращает объект Type, который представляет элемент; методы GetInterfaces и GetInterface возвращают объект Type, который представляет интерфейс, поддерживаемый типом;метод GetTypeArray возвращает массив объектов Type, которые представляют типы, заданные посредством набора объектов;методы GetTypeFromProgIDи GetTypeFromCLSID возвращают объект Type , который указывается через ProgID или CLSID (методы предоставляются для совместимости с СОМ);метод GetTypeFromHandle возвращает объект Type , который указывается посредством дескриптора (метод предоставляется для совместимости);оператор typeof получает объект Type для указанного типа.
Метаданные - это информация о выполнимом модуле, получаемая во время выполнения приложения. К такой информации относятся и данные о типе. В случае неправильного указания имени типа возникает исключение. Поэтому указание типа следует заключать в блок try-catch .
Например:
try { Type tp=Type.GetType(s); Console.WriteLine("Имя типа : {0}",tp.FullName); } catch (System.NullReferenceException) {Console.WriteLine("Ошибка задания типа");}
Класс Type предоставляет большой набор свойств для запроса информации по типу, включая следующие:
FullName - возвращает имя типа;IsClass - определяет, является ли тип классом;IsAbstract - определяет, является ли тип абстрактным классом;IsNestedPublic - определяет, является ли тип вложенным и общедоступным;IsPublic - определяет, является ли данный тип общедоступным;IsNotPublic - определяет, является ли данный тип защищенным;IsSealed - определяет, является ли тип конечным (не может быть базовым классом);IsArray - определяет, представляет ли указанный тип массив;GUID - возвращает значение типа GUID, ассоциированное с данным типом (такое значение хранится в реестре Windows).
Например:
Type myType = typeof(MyClass1); Guid myGuid =(Guid) myType.GUID;
IsNestedAssembly - определяет, является ли тип вложенным и видимым только в своей собственной сборке;Module - возвращает модуль (DLL) в котором объявлен данный тип;Namespace - возвращает пространство имен, содержащее данный тип;
Свойство IsByRef позволяет определить, передается ли указанный элемент по типу, а свойство Assembly определяет сборку.
Например:
Type tp=Type.GetType(s); Console.WriteLine("\tПередается по ссылке ? : {0}",tp.IsByRef);
Методы-аксессоры
После определения параметра в фигурных скобках указаны два метода-аксессора. get-аксессор возвращает значение данных по указанному индексу,а set-аксессор устанавливает значение данных с указанным индексом. Устанавливаемое значение задается ключевым словом value.
Индексатор устанавливает и возвращает значения некоторого массива. Такой массив для аксессора должен быть создан. Типы используемого массива и аксессора должны совпадать. Например для целочисленного аксессора можно объявить следующий массив:
private int [] imyArray = new int[50];
Теперь, чтобы использовать акссессор, следует:
определить возвращаемое методом-аксессором значение (например: return imyArray[ind1];);определить устанавливаемое методом-аксессором значение (например: imyArray[ind1]= value;).
В результате класс, содержащий аксессор, будет иметь следующее объявление:
public class AClass1 { public AClass1() { } private int [] imyArray = new int[20];
public int this[int ind1] { get { return imyArray[ind1]; } set { imyArray[ind1]= value; } } }
Назначение атрибута
Для того, чтобы назначить атрибут элементу кода, можно:
определить новый класс атрибутаиспользовать существующий класс атрибута из библиотеки NET Framework.
Атрибут указывается в квадратных скобках перед элементом, которому он назначается. Назначаемый атрибут инициализируется вызовом конструктора с соответствующими параметрами.
Класс System.ObsoleteAttribute позволяет помечать код и задавать информацию, которая будет отображаться как Warning во время компиляции приложения. Этот класс предназначается для возможности указания некоторого кода модуля как "устаревшего".Для того чтобы использовать существующий класс для назначаемого методу атрибута, следует:
создать метод, использующий атрибут (например, метод, при каждом вызове которого компилятор будет формировать сообщение Warning с указанным в атрибуте кодом);ввести перед определением метода в квадратных скобках имя класса атрибута (например, класса ObsoleteAttribute).
Например:
[ObsoleteAttribute ("Сообщение, отображаемое компилятором как Warning")] public static void M1( ) {return ; // Компилятор будет выдавать предупреждение при // каждом вызове данного метода, которому назначен // соответствующий атрибут. Например: // c:\c#_project\pr1\class1.cs(23,4): warning // CS0618: 'pr1.Class1.M1()' is obsolete: // 'Сообщение, отображаемое компилятором как // Warning' }
Язык С# при назначении атрибута позволяет не указывать суффикс Attribute. Так, вместо [ObsoleteAttribute("")] можно записать [Obsolete("")].
Класс атрибута должен иметь хотя бы один public-конструктор.
Параметры атрибута
По умолчанию код класса атрибута содержит конструктор без параметров.Для того чтобы иметь возможность при назначении атрибута сохранять для типа или члена класса некоторые параметры, эти параметры надо задать как параметры конструктора класса атрибута. Параметры атрибута могут объявляться как переменные члены класса.
Для доступа к защищенным переменным членам класса в языке C# используются свойства.
Для создания свойства в среде проектирования VisualStudio.NET можно использовать мастер построения свойства: для этого в окне Class View следует выделить секцию c именем класса и выполнить команду контекстного меню Add|Add Property.
В диалоге C# Property Wizard (рис. 17.2) в поле Property Name следует указать имя создаваемого свойства.
Рис. 17.2. Диалог C# Property Wizard
Тип создаваемого свойства выбирается из списка Property type.
На панели Accessors переключатели get и set определяют, какие методы-аксессоры будут созданы. Например:
using System; namespace MyPr1 { [AttributeUsage(AttributeTargets.All)] public class MyAttribute : System.Attribute { private string name; // Используются как private int kod; // параметры атрибута public MyAttribute(string name) { // Конструктор this.name = name; this.kod = 12345; } // Свойство Name public string Name { get { return name; } } // Свойство Kod public int Kod { get { return kod; } set {kod=value; } // Назначение // защищенной переменной члену // класса значения параметра } } } // Для назначения некоторому классу данного // атрибута перед объявлением класса следует // указать строку типа [My("NameClass", Kod=123)]
Получение информации о методах
Используя метод GetMetods, можно получить информацию о методах. Такая информация заносится в массив типа MetodInfo.
Например:
public static void Main() { Type myType =(typeof(MyClass1)); // Получить методы с доступом public MethodInfo[] myArrMethodInfo = myType.GetMethods(BindingFlags.Public |BindingFlags.Instance |BindingFlags.DeclaredOnly); Console.WriteLine("\n Число методов public =:" +myArrMethodInfo.Length); Console.WriteLine("Имена методов public : "); // Отобразить имена всех методов MyPrintMethodInfo(myArrMethodInfo); // Получить методы с защищенным доступом MethodInfo[] myArrMethodInfo1 = myType.GetMethods(BindingFlags.NonPublic |BindingFlags.Instance |BindingFlags.DeclaredOnly); Console.WriteLine("\n Число защищенных методов:" +myArrMethodInfo1.Length); } public static void MyPrintMethodInfo(MethodInfo[] a) { for(int i=0;i<a.Length;i++) { MethodInfo myMethodInfo = (MethodInfo)a[i]; Console.WriteLine("\n" + myMethodInfo.Name); } }
Листинг 17.2.
Позиционные и именованные параметры атрибута
При назначении классу или члену класса атрибута используется конструктор атрибута со списком параметров. Параметры могут быть:
позиционными;именованными.
Позиционные параметры указываются в порядке, который определяется списком параметров конструктора атрибута. Позиционные параметры всегда должны быть указаны при назначении атрибута.
Именованные параметры отсутствуют в списке параметров конструктора атрибута. Значения, задаваемые для именованных параметров, используются для инициализации полей и свойств создаваемого экземпляра атрибута. Список именованных параметров указывается через запятую после списка позиционных параметров. Каждый именованный параметр определяется как Имя_параметра=Значение_параметра.
В предыдущем примере параметр name использовался как позиционный параметр, а kod - как именованный (по умолчанию значение переменной kod, доступной через свойство Kod, устанавливается равным конкретному значению. Если при назначении атрибута явно не будет задан именованный параметр, то при создании экземпляра атрибута будет использовано значение по умолчанию).
Параметры атрибута могут указываться константными значениями следующих типов:
bool, byte, char, short, int, long, float, double;string; System.Type; enums; object (аргумент для параметра атрибута типа object должен быть константным значением вышеуказанных типов);одноразмерные массивы любых вышеуказанных типов.
Создание атрибута
Создание атрибута начинается с создания класса, реализующего атрибут. Такой класс должен быть наследуем от любого класса атрибута. Класс атрибута всегда должен иметь модификатор доступа public.
Класс атрибута всегда непосредственно или опосредованно наследуется от класса System.Attribute.
Например:
public class MyAttribute : System.Attribute { public MyAttribute() { } }
Создание индексаторов
Индексатор позволяет работать с классом или структурой таким образом, как если бы это были массивы. Индексация класса выполняется по индексу, указываемому как параметр. Иногда классы, используемые как индексаторы, называют классами-индексаторами.
Объявление индексатора может иметь следующее формальное описание:
атрибуты] [модификаторы] тип this [[атрибуты] тип_параметра идентификатор_параметра .,...] {объявление аксессоров}
Индексатор должен иметь как минимум один параметр. Тип и идентификатор параметра указываются в квадратных скобках после ключевого слова this.
Среда проектирования Visual Studio.NET позволяет использовать мастер создания индексатора: для этого в окне Class View следует выделить имя класса и выполнить команду контекстного меню Add|Add Indexer.
Диалог C# Indexer Wizard (рис. 17.1) позволяет определить параметры создаваемого индексатора.
Рис. 17.1. Диалог C# Indexer Wizard
В поле Indexer Аcess указывается модификатор доступа для индексатора. Это могут быть следующие модификаторы:
public - доступ не ограничен;protected - доступ ограничен только наследуемыми классами;internal - доступ ограничен рамками текущего проекта;private - доступ ограничен рамками данного класса;protected internal - ограничение доступа наследуемыми классами текущего проекта.
В поле Indexer Type определяется тип массива, используемого для индексатора. В качестве типа индексатора может быть выбран любой из следующих типов:
bool - логическое значение true или false;decimal - 128-битовый тип данных (1,0 * 10-28 до 7,9 * 1028);int - 32-битовое целочисленное значение;sbyte - знаковое 8-битовое целое (от - 128 до 127);uint - беззнаковое 32-битовое целочисленное значение (от 0 до 4 294 967 295);byte;double - 64-битовый тип данных (±5,0 * 10-324 до ±1,7 * 10308);long - 64-битовое целочисленное значение;string;ulong;char;float- 32-битовый тип данных (±1.5 * 10-45 до ±3.4 * 1038);object;short;ushort.
Поле Parameter Name содержит имя параметра.
На панели Indexer Modifiers можно выбрать одну из следующих опций:
None - индексатор не содержит дополнительных модификаторов;Virtual - реализация индексатора может быть переопределена в наследуемых классах, для индексатора указывается ключевое слово virtual;Abstract - индексатор является членом абстрактного класса, для индексатора указывается ключевое слово abstract.
Ключевое слово this используется как имя индексатора, так как с классом, содержащим индексатор, можно манипулировать как с массивом.
Например:
public int this[int ind1] // Индексатор типа int // с одним параметром типа int - ind1 { get { return 0; } set { } }
DLL-библиотеки
Для того чтобы вызвать метод из DLL-библиотеки, его следует объявить с модификатором extern и атрибутом DllImport.
Например:
[DllImport("Имя_библиотеки.dll")] static extern int M1(int i1, string s1);
Класс атрибута DllImportAttribute имеет следующее определение:
namespace System.Runtime.InteropServices { [AttributeUsage(AttributeTargets.Method)] public class DllImportAttribute: System.Attribute { public DllImportAttribute(string dllName) {...} public CallingConvention CallingConvention; public CharSet CharSet; // Набор символов public string EntryPoint; // Имя метода public bool ExactSpelling; // Точное // соответствие написания имени метода public bool PreserveSig; // Определяет, будет ли // предотвращено изменение сигнатуры метода (по умолчанию // установлено значение true). // При изменении сигнатуры возвращаемое значение будет // иметь тип HRESULT и будет добавлен out-параметр retval public bool SetLastError; public string Value { get {...} } } }
Атрибут DllImport имеет один позиционный параметр, определяющий имя DLL-библиотеки, и пять именованных параметров. Параметр EntryPoint позволяет указать имя метода из DLL-библиотеки. При этом имя метода, помечаемого данным атрибутом, может отличаться от имени метода в DLL-библиотеке.
Например:
[DllImport("myDll.dll", EntryPoint="M1")] static extern int New_name_of_M1(int i1, string s1);
Именованный параметр CharSet определяет используемый в DLL-библиотеке набор символов (ANSI или Unicode). По умолчанию используется значение CharSet.Auto.
Например:
[DllImport("myDll.dll", CharSet CharSet.Ansi)] static extern int M1(int i1, string s1);
Для каждого типа параметра при вызове метода из DLL-библиотеки выполняющая среда .NET производит подразумеваемое по умолчанию преобразование типов (например, тип string в тип LPSTR (Win32). Для того чтобы явным образом указать тип, используемый в методе DLL-библиотеки, следует применить к параметру атрибут MarshalAsAttribute.
Например:
[DllImport("myDll.dll", CharSet CharSet.Unicode)] static extern int M1(int i1, [MarshalAs(UnmanagedType.LPWStr)] string s1);
Атрибут MarshalAsAttribute может быть прикреплен к полю, методу или параметру. Прикрепление данного атрибута к методу позволяет указать явное преобразование типа для возвращаемого значения.
Параметр callback используется для вызова
using System; namespace MyDelegatе1 { class Class1 {[STAThread] static void Main(string[] args) { CA var1= new CA(); // Создание экземпляра делегата CA.Metod1Callback myCall = new CA.Metod1Callback(Class1.Metod2); // Значение // параметра, передаваемое методу обратного // вызова Class1.Metod2, определено // в методе Metod1 как "1". // Выполнение метода обратного вызова (Metod2), // переданного в качестве параметра CA.Metod1(myCall); } static void Metod2 (string str2){ Console.WriteLine("Выполняется метод Metod2"); Console.WriteLine(str2); } } } using System; namespace MyDelegatе1 { public class CA { public CA() { } public delegate void Metod1Callback(string str1); public static void Metod1(Metod1Callback callback) { Console.WriteLine("Выполняется метод Metod1"); // Параметр callback используется для вызова // метода обратного вызова и передачи ему // параметра (строки "1") callback("1"); } } } |
Листинг 18.1. |
Закрыть окно |
using System;
namespace MyDelegatе1
{
class Class1
{[STAThread]
static void Main(string[] args) {
CA var1= new CA();
// Создание экземпляра делегата
CA.Metod1Callback myCall =
new CA.Metod1Callback(Class1.Metod2); // Значение
// параметра, передаваемое методу обратного
// вызова Class1.Metod2, определено
// в методе Metod1 как "1".
// Выполнение метода обратного вызова (Metod2),
// переданного в качестве параметра
CA.Metod1(myCall);
}
static void Metod2 (string str2){
Console.WriteLine("Выполняется метод Metod2");
Console.WriteLine(str2);
}
}
}
using System;
namespace MyDelegatе1
{
public class CA
{ public CA() { }
public delegate void Metod1Callback(string str1);
public static void Metod1(Metod1Callback callback)
{
Console.WriteLine("Выполняется метод Metod1");
// Параметр callback используется для вызова
// метода обратного вызова и передачи ему
// параметра (строки "1")
callback("1");
}
}
}
Использование делегата для вызова методов
Делегат объявляет новый ссылочный тип.
Делегат позволяет передавать функцию как параметр.
Объявление делегата имеет следующее формальное описание:
[атрибуты] [модификаторы] delegate тип_результата имя_делегата ([список_формальных параметров]);
Модификаторами делегата могут быть:
newpublicprotectedprivateinternalunsafe (если в списке параметров используется указатель).
Тип результата должен соответствовать типу результата используемого метода. При создании делегата требуется, чтобы передаваемый как делегат метод имел ту же сигнатуру метода, что и в объявлении делегата.
Например:
using System; delegate void MyDelegate(); // Этот делегат позволяет // вызывать методы без параметров // и без возвращаемого значения
Для вызова метода через делегата следует создать экземпляр делегата, передав ему в качестве параметра метод, имеющий ту же сигнатуру, что и у делегата, а затем выполнить вызов. Для статического метода в качестве параметра передается имя метода, квалифицированное именем класса.
Например:
using System; delegate void MyDelegate(); namespace MyDelegat1 { class Class1 {[STAThread] static void Main(string[] args) { CA var1= new CA(); // Экземпляр делегата для нестатического метода: MyDelegate F_d = new MyDelegate(var1.F_Instance); F_d(); // Экземпляр делегата для статического метода: F_d = new MyDelegate(CA.F_Static); F_d(); } } public class CA { public CA() { } public void F_Instance() { Console.WriteLine("Вызов метода класса с использованием делегата"); } public static void F_Static() { Console.WriteLine("Вызов статического метода с использованием делегата"); } }
Небезопасный код
Фрагмент небезопасного кода следует помечать ключевым словом unsafe.
Например:
int i1; unsafe {int *i2=&i1;}
Ключевым словом unsafe требуется обязательно помечать любой фрагмент кода, который содержит указатель.
Модификатор unsafe может быть указан для методов, свойств и конструкторов (за исключением статических конструкторов), а также для блоков кода.
Например:
using System; class Class1 { unsafe static void M1 (int* p) // Небезопасный код: указатель на int { *p *= *p; } // *p - доступ к значению unsafe public static void Main() // Небезопасный код: применен оператор // получения адреса (&) { int i = 4; M1 (&i); Console.WriteLine (i); } }
Чтобы использовать небезопасный код, следует установить опцию компилятора /unsafe. Для этого достаточно выбрать имя проекта в окне Solution Explorer и через контекстное меню вызвать диалогProperty Pages (рис. 18.1) , а затем на странице Configuration Properties | Build установить значение опции Allow unsafe code blocks равным True.
Рис. 18.1. Диалог Property Pages
Указатели можно использовать только с размерными типами, массивами и строками. При задании указателя на массив первый элемент массива должен быть размерного типа.
Выполняющая среда .NET для эффективного управления памятью при удалении одних объектов "перемещает" другие объекты, чтобы исключить фрагментацию памяти свободными блоками памяти. Таким образом, выполняющая среда .NET по умолчанию не гарантирует, что объект, на который получен указатель, всегда будет иметь один и тот же адрес. Для предотвращения перемещения объекта следует использовать оператор fixed, который имеет следующее формальное описание:
fixed ( тип* имя_указателя = выражение ) выполняемый_оператор_или_блок
В качестве типа можно указывать любой неуправляемый тип или void. Выражение является указателем на заданный тип. Фиксация объекта применяется только для указанного выполняемого оператора или блока. Доступ к фиксированной переменной не ограничен областью видимости небезопасного кода.
Поэтому фиксированная переменная может указывать на значение, располагаемое в более широкой области видимости, чем данная область видимости небезопасного кода. При этом компилятор C# не выдает предупреждений о такой ситуации.
Однако компилятор C# не позволит установить указатель на управляемую переменную вне оператора fixed.
Например:
class Point { public int x, y; } class Class1 {[STAThread] static void Main(string[] args) { unsafe { Point pt = new Point(); // pt - это управляемая // переменная fixed ( int* p = &pt.x ) { *p = 1 } // pt.x - // указывает на значение // размерного типа } } }
Одним оператором fixed можно фиксировать несколько указателей, но только в том случае, если они одного типа.
Например:
fixed (byte* pa1 = array1, pa2 = array2) {...}
В том случае, если требуется зафиксировать указатели различных типов, можно использовать вложенные операторы fixed.
Например:
fixed (int* p1 = &p.x) fixed (double* p2 = &array1[5]) { }
Указатели, которые инициализированы оператором fixed, не могут быть изменены. Если объект, на который устанавливается указатель, размещается в стеке (например, переменная типа int), то его местоположение фиксировать не требуется.
Разместить блок памяти в стеке можно и явным образом, используя оператор stackalloc, который имеет следующее формальное описание:
тип * имя_указателя = stackalloc тип [ выражение ];
В качестве типа может быть указан любой неуправляемый тип.
Например:
using System; class Class1 {public static unsafe void Main() { int* a1 = stackalloc int[100]; int* p = a1; // Указатель на первый // элемент массива *p++ = *p++ = 1; for (int i=2; i<100; ++i, ++p) *p = p[-1] + p[-2]; for (int i=0; i<10; ++i) Console.WriteLine (a1[i]); } }
Время жизни указателя, инициализированного с применением stackalloc, ограничено временем выполнения метода, в котором этот указатель объявлен. Инициализировать указатели посредством stackalloc можно только для локальных переменных.
Применение делегатов как методов обратного вызова
Метод обратного вызова используется для передачи одному методу в качестве параметра другого метода, который может быть вызван через переданный "указатель" на метод.
Методы обратного вызова могут применяться в различных целях. Наиболее часто их используют для реализации асинхронной обработки данных или определения кода, выполняющего дополнительную обработку данных.
Например:
using System; namespace MyDelegatе1 { class Class1 {[STAThread] static void Main(string[] args) { CA var1= new CA(); // Создание экземпляра делегата CA.Metod1Callback myCall = new CA.Metod1Callback(Class1.Metod2); // Значение // параметра, передаваемое методу обратного // вызова Class1.Metod2, определено // в методе Metod1 как "1".
// Выполнение метода обратного вызова (Metod2), // переданного в качестве параметра CA.Metod1(myCall); } static void Metod2 (string str2){ Console.WriteLine("Выполняется метод Metod2"); Console.WriteLine(str2); } } } using System; namespace MyDelegatе1 { public class CA { public CA() { } public delegate void Metod1Callback(string str1); public static void Metod1(Metod1Callback callback) { Console.WriteLine("Выполняется метод Metod1"); // Параметр callback используется для вызова // метода обратного вызова и передачи ему // параметра (строки "1") callback("1"); } } }
Листинг 18.1.
В результате выполнения этого приложения сначала будет вызван метод Metod1, а затем Metod2.
Применение неуправляемого кода
По умолчанию приложения на C# относятся к управляемому коду. Но при необходимости управляемый код может взаимодействовать с неуправляемым кодом. К неуправляемому коду, вызываемому из управляемых C# приложений, можно отнести функции DLL-библиотек и сервисы COM-компонентов. Приложение управляемого кода на языке C# также может включать фрагменты небезопасного кода. Небезопасный код тоже относится к неуправляемому коду, так как выделение и освобождение памяти в нем не контролируется исполняющей средой .NET.
Класс диалога имеет следующее объявление
// Заголовочный файл class CD1App : public CWinApp {public: CD1App(); public: virtual BOOL InitInstance(); // Первый // выполняемый метод // Implementation DECLARE_MESSAGE_MAP() }; extern CD1App theApp; // Переменная - приложение // Файл реализации D1.cpp #include "stdafx.h" #include "D1.h" #include "D1Dlg.h" // CD1App BEGIN_MESSAGE_MAP(CD1App, CWinApp) // Обрабатываемые события ON_COMMAND(ID_HELP, &CWinApp::OnHelp) END_MESSAGE_MAP() CD1App::CD1App(){ } // Конструктор CD1App theApp; BOOL CD1App::InitInstance() { CWinApp::InitInstance(); AfxEnableControlContainer(); SetRegistryKey(_T("Local AppWizard-Generated Applications")); CD1Dlg dlg; // Создание объекта диалога m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); // Отображение // диалога if (nResponse == IDOK) { } else if (nResponse == IDCANCEL) { } return FALSE; // Завершение приложения } Класс диалога имеет следующее объявление и реализацию: // Заголовочный файл class CD1Dlg : public CDialog {public: CD1Dlg(CWnd* pParent = NULL); // Конструктор enum { IDD = IDD_D1_DIALOG }; // Ресурс диалога protected: // Поддержка DDX/DDV: virtual void DoDataExchange(CDataExchange* pDX); // Реализация protected: HICON m_hIcon; // Функции таблицы обрабатываемых событий virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() }; // Файл реализации D1Dlg.cpp #include "stdafx.h" #include "D1.h" #include "D1Dlg.h" class CAboutDlg : public CDialog // Класс // вспомогательного диалога {public: CAboutDlg(); enum { IDD = IDD_ABOUTBOX }; // Ресурс диалога protected: virtual void DoDataExchange(CDataExchange* pDX); protected: DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX);} BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) END_MESSAGE_MAP() // Класс окна диалога приложения CD1Dlg::CD1Dlg(CWnd* pParent /*=NULL*/) : CDialog(CD1Dlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CD1Dlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CD1Dlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() END_MESSAGE_MAP() // Методы обработки событий BOOL CD1Dlg::OnInitDialog() { CDialog::OnInitDialog(); ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); // Указатель // на системное меню if (pSysMenu != NULL) // Добавление пунктов к { CString strAboutMenu; //системному меню strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } SetIcon(m_hIcon, TRUE); // Определение крупной // пиктограммы SetIcon(m_hIcon, FALSE); // Определение мелкой пиктограммы return TRUE; } void CD1Dlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } } void CD1Dlg::OnPaint() { CDialog::OnPaint(); } HCURSOR CD1Dlg::OnQueryDragIcon() // Запрос курсора { return static_cast<HCURSOR>(m_hIcon); } |
Листинг 19.1. |
Закрыть окно |
END_MESSAGE_MAP()
// Класс окна диалога приложения
CD1Dlg::CD1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(CD1Dlg::IDD, pParent)
{ m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
void CD1Dlg::DoDataExchange(CDataExchange* pDX)
{ CDialog::DoDataExchange(pDX); }
BEGIN_MESSAGE_MAP(CD1Dlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
END_MESSAGE_MAP()
// Методы обработки событий
BOOL CD1Dlg::OnInitDialog()
{ CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE); // Указатель
// на системное меню
if (pSysMenu != NULL) // Добавление пунктов к
{ CString strAboutMenu; //системному меню
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{ pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,
IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE); // Определение крупной
// пиктограммы
SetIcon(m_hIcon, FALSE); // Определение мелкой пиктограммы
return TRUE;
}
void CD1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{ if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{ CAboutDlg dlgAbout; dlgAbout.DoModal(); }
else { CDialog::OnSysCommand(nID, lParam); }
}
void CD1Dlg::OnPaint() { CDialog::OnPaint(); }
HCURSOR CD1Dlg::OnQueryDragIcon() // Запрос курсора
{ return static_cast(m_hIcon); }
данного диалога или NULL HINSTANCE
typedef struct tagOFN { DWORD lStructSize; // Длина структуры в байтах HWND hwndOwner; // Дескриптор окна владельца // данного диалога или NULL HINSTANCE hInstance; LPCTSTR lpstrFilter; // Указатель на буфер, содержащий // пары null-ограниченных строк: // первая строка - это описание // фильтра (например: "Программы"), // вторая строка - шаблон // (например: "*.exe;*.com")" LPTSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPTSTR lpstrFile; // Указатель на буфер, // который содержит // имя файла, используемое для // инициализации поля File Name, // или NULL. При успешном завершении // функции GetOpenFileName или //GetSaveFileName поле содержит // логический диск, путь и имя // выбранного файла. DWORD nMaxFile; LPTSTR lpstrFileTitle; // Указатель на буфер, // содержащий имя и расширение выбранного файла DWORD nMaxFileTitle; LPCTSTR lpstrInitialDir; // Указатель на null- // ограниченную строку, которая представляет каталог, // используемый для инициализации диалога LPCTSTR lpstrTitle; DWORD Flags; WORD nFileOffset; WORD nFileExtension; LPCTSTR lpstrDefExt; LPARAM lCustData; LPOFNHOOKPROC lpfnHook; LPCTSTR lpTemplateName; #if (_WIN32_WINNT >= 0x0500) void * pvReserved; DWORD dwReserved; DWORD FlagsEx; #endif // (_WIN32_WINNT >= 0x0500) } OPENFILENAME, *LPOPENFILENAME; |
Листинг 19.2. |
Закрыть окно |
typedef struct tagOFN {
DWORD lStructSize; // Длина структуры в байтах
HWND hwndOwner; // Дескриптор окна владельца
// данного диалога или NULL
HINSTANCE hInstance;
LPCTSTR lpstrFilter; // Указатель на буфер, содержащий
// пары null-ограниченных строк:
// первая строка - это описание
// фильтра (например: "Программы"),
// вторая строка - шаблон
// (например: "*.exe;*.com")"
LPTSTR lpstrCustomFilter;
DWORD nMaxCustFilter;
DWORD nFilterIndex;
LPTSTR lpstrFile; // Указатель на буфер,
// который содержит
// имя файла, используемое для
// инициализации поля File Name,
// или NULL. При успешном завершении
// функции GetOpenFileName или
//GetSaveFileName поле содержит
// логический диск, путь и имя
// выбранного файла.
DWORD nMaxFile;
LPTSTR lpstrFileTitle; // Указатель на буфер,
// содержащий имя и расширение выбранного файла
DWORD nMaxFileTitle;
LPCTSTR lpstrInitialDir; // Указатель на null-
// ограниченную строку, которая представляет каталог,
// используемый для инициализации диалога
LPCTSTR lpstrTitle;
DWORD Flags;
WORD nFileOffset;
WORD nFileExtension;
LPCTSTR lpstrDefExt;
LPARAM lCustData;
LPOFNHOOKPROC lpfnHook;
LPCTSTR lpTemplateName;
#if (_WIN32_WINNT >= 0x0500)
void * pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAME, *LPOPENFILENAME;
Класс CColorDialog
Класс CColorDialog предназначается для реализации стандартного диалога Color, используемого для выбора цвета. Структура CHOOSECOLOR применяется для инициализации диалога. Для отображения стандартного диалога Color вызывается метод ChooseColor.
Например:
CHOOSECOLOR cc; // Структура для инициализации диалога static COLORREF acrCustClr[16]; // Массив цветов HWND hwnd; // Окно владелец HBRUSH hbrush; // Указатель кисти static DWORD rgbCurrent; // Первоначально выбранный цвет ZeroMemory(&cc, sizeof(cc)); cc.lStructSize = sizeof(cc); cc.hwndOwner = hwnd; cc.lpCustColors = (LPDWORD) acrCustClr; cc.rgbResult = rgbCurrent; cc.Flags = CC_FULLOPEN | CC_RGBINIT;
if (ChooseColor(&cc)==TRUE) { hbrush = CreateSolidBrush(cc.rgbResult); rgbCurrent = cc.rgbResult; }
Класс CDialog
Класс CDialog является базовым классом, используемым для отображения на экране диалоговых окон, называемых также диалогами.
Есть два типа диалоговых окон - модальные и немодальные.
Как уже отмечалось, для реализации диалога следует создать в редакторе диалога шаблон диалогового окна, а затем с помощью ClassWizard (или вручную) создать производный класс от CDialog и определить в нем необходимые методы - обработчики сообщений для расположенных в используемом шаблоне диалога элементов управления.
ClassWizard автоматически будет добавлять список обрабатываемых сообщений в таблицу сообщений производного класса, а в заголовочном файле будет указан макрос DECLARE_MESSAGE_MAP (), который указывает, что класс содержит таблицу сообщений.
Для создания диалоговых окон на основе существующего ресурса диалога можно использовать:
метод DoModal (для модального диалога);метод Create (для немодального диалога);
Если шаблон диалога отсутствует, то можно создать шаблон диалога, используя структуру DLGTEMPLATE, а затем, после создания объекта диалога, вызвать методы CreateIndirect или InitModalIndirect и DoModal.
Модальное диалоговое окно автоматически закрывается при щелчке пользователя на кнопках типа OK или Cancel или при вызове метода EndDialog. Отметим, что метод EndDialog только скрывает окно диалога, а для его удаления следует использовать функцию DestroyWindow.
Для обмена значениями с элементом управления используется метод CWnd::UpdateData. Этот метод автоматически вызывается и при щелчке пользователя на кнопке типа OK, и при инициализации элементов управления методом OnInitDialog.
Метод OnInitDialog вызывается после создания всех элементов управления диалогового окна, но перед их отображением.
Для определения фона диалоговых окон используется метод CWinApp::SetDialogBkColor.
Последовательность создания диалогового окна включает следующие шаги:
проектирование в редакторе диалога шаблона диалогового окна с включением в него всех требуемых элементов управления и настройки их свойств;создание класса, производного от CDialog, с описанием в нем переменных и обработчиков событий, а также указанием используемого шаблона диалога;определение для каждого элемента управления переменной или объекта, используемых для доступа к значению данного элемента управления;определение необходимых методов обработчиков событий для каждого элемента управления;создание объекта разработанного производного класса диалога (фактически вызов конструктора) и создание диалогового окна:
вызов конструктора с параметром, указывающим ID шаблона диалога | вызов конструктора без параметра шаблона диалога |
вызов метода DoModal | вызов метода Create с параметром, указывающим ID шаблона диалога |
Класс CDialog предоставляет следующие конструкторы объекта:
CDialog (LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL); CDialog (UINT nIDTemplate, CWnd* pParentWnd = NULL); CDialog ();
Параметр lpszTemplateName - это строка, содержащая имя ресурса шаблона диалога, nIDTemplate - значение ID ресурса шаблона диалога. По умолчанию редактор ресурсов присваивает шаблонам диалога идентификаторы, начинающиеся с префикса IDD_. Параметр pParentWnd задает указатель на родительское окно или окно собственник (типа CWnd), которому принадлежит этот диалог. Значение NULL показывает, что родительским окном является главное окно приложения.
Конструктор без параметров используется для создания модального окна диалога на основе шаблона, расположенного в памяти. При этом применяется метод InitModalIndirect. Для создания модального диалога после вызова конструктора следует вызвать метод DoModal.
Класс CDialog предоставляет следующие методы Create для создания немодального диалога:
BOOL Create (LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL); BOOL Create (UINT nIDTemplate, CWnd* pParentWnd = NULL);
При успешном завершении создания диалогового окна и инициализации эти методы возвращают ненулевое значение.
Если в шаблоне диалога не установлен стиль WS_VISIBLE, то для отображения диалога необходимо вызвать метод ShowWindow.
Класс CDialog предоставляет большой набор методов, предназначенных для работы с диалоговым окном, включая следующие:
DoModal - используется для создания модального диалогового окна.NextDlgCtrl - перемещает фокус на следующий элемент управления диалогового окна (а с последнего элемента управления - на первый).PrevDlgCtrl - перемещает фокус на предыдущий элемент управления диалогового окна (а с первого элемента управления - на последний);GotoDlgCtrl (CWnd* pWndCtrl) - перемещает фокус на указываемый параметром pWndCtrl элемент управления (чтобы получить значение параметра pWndCtrl, можно вызвать метод CWnd::GetDlgItem).SetDefID (UINT nID) - изменяет командную кнопку, используемую по умолчанию (параметр задает ID командной кнопки).SetHelpID (UINT nIDR) - определяет контекстно-зависимый идентификатор справки (ID) для диалогового окна.EndDialog - используется для закрытия модального диалогового окна.OnInitDialog - вызывается для обработки сообщения WM_INITDIALOG, которое инициируется при выполнении методов Create, CreateIndirect или DoModal.При переопределении этого метода первоначально должен быть вызван метод OnInitDialog базового класса. При успешном завершении метод возвращает значение TRUE.OnSetFont (CFont* pFont) - определяет шрифт, используемый по умолчанию для всех элементов управления диалогового окна.OnOK - обработчик сообщения, вызываемый при щелчке пользователя на командной кнопке OK (с идентификатором IDOK).OnCancel - обработчик сообщения, вызываемый при щелчке пользователя на командной кнопке Cancel (с идентификатором IDCANCEL).
Класс CFileDialog
Класс CFileDialog инкапсулирует поведение диалогов Open и Save As. Для работы с объектом типа CFileDialog сначала следует создать этот объект, используя конструктор, а затем управлять данным компонентом через поля структуры m_ofn типа OPENFILENAME.
Структура OPENFILENAME содержит информацию, используемую функциями GetOpenFileName и GetSaveFileName для инициализации диалогов Open или Save As .
Структура OPENFILENAME имеет следующее определение:
typedef struct tagOFN { DWORD lStructSize; // Длина структуры в байтах HWND hwndOwner; // Дескриптор окна владельца // данного диалога или NULL HINSTANCE hInstance; LPCTSTR lpstrFilter; // Указатель на буфер, содержащий // пары null-ограниченных строк: // первая строка - это описание // фильтра (например: "Программы"), // вторая строка - шаблон // (например: "*.exe;*.com")" LPTSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPTSTR lpstrFile; // Указатель на буфер, // который содержит // имя файла, используемое для // инициализации поля File Name, // или NULL. При успешном завершении // функции GetOpenFileName или //GetSaveFileName поле содержит // логический диск, путь и имя // выбранного файла. DWORD nMaxFile; LPTSTR lpstrFileTitle; // Указатель на буфер, // содержащий имя и расширение выбранного файла DWORD nMaxFileTitle; LPCTSTR lpstrInitialDir; // Указатель на null- // ограниченную строку, которая представляет каталог, // используемый для инициализации диалога LPCTSTR lpstrTitle; DWORD Flags; WORD nFileOffset; WORD nFileExtension; LPCTSTR lpstrDefExt; LPARAM lCustData; LPOFNHOOKPROC lpfnHook; LPCTSTR lpTemplateName; #if (_WIN32_WINNT >= 0x0500) void * pvReserved; DWORD dwReserved; DWORD FlagsEx; #endif // (_WIN32_WINNT >= 0x0500) } OPENFILENAME, *LPOPENFILENAME;
Листинг 19.2.
Если метод DoModal, используемый для отображения диалога типа CFileDialog, возвращает значение IDOK, то для получения информации об имени файла можно использовать методы члены класса CFileDialog.
Если предполагается разрешить пользователю одновременно выбрать более одного файла, то до вызова метода DoModal следует установить флаг OFN_ALLOWMULTISELECT.
Например:
OPENFILENAME ofn; // Данные для диалога char szFile[260]; // Буфер для имени файла HWND hwnd; // Окно - владелец отображаемого диалога HANDLE hf; // Дексриптор файла // Инициализация структуры OPENFILENAME ZeroMemory(&ofn, sizeof(OPENFILENAME)); // Макрос, // заполняющий нулями указанный блок памяти ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFilter = "Все\0*.*\0Текстовые\0*.TXT\0"; ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; // Отображение диалога Open if (GetOpenFileName(&ofn)==TRUE) hf = CreateFile(ofn.lpstrFile, GENERIC_READ, 0, (LPSECURITY_ATTRIBUTES) NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL);
Класс CFileDialog содержит поле m_ofn, которое является структурой типа OPENFILENAME, используемой для инициализации диалогов Open и Save As.
Для создания данного диалога применяется следующий конструктор:
CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL, DWORD dwSize = sizeof( OPENFILENAME ));
Параметр bOpenFileDialogопределяет типа создаваемого диалога: значение TRUE указывает диалог Open, а FALSE - диалог Save As. Параметр lpszDefExt указывает используемое по умолчанию расширение файла. Если пользователь не вводит расширение файла в поле File name, то данное значение автоматически добавляется к имени файла. Если значение этого параметра равно NULL, то расширение не добавляется. Параметр lpszFileName - это NULL или имя файла, отображаемое при открытии данного диалога. Параметр dwFlags определяет флажки, используемые для настройки диалога; lpszFilter - это пары строк, определяющие фильтры для выбора файлов. При использовании структуры типа OPENFILENAME строки фильтра следует ограничивать символом '\0', а в данном параметре следует использовать символ |.
Параметр pParentWnd- это указатель на окно владелец или родительское окно данного диалога. Размер структуры OPENFILENAME указывается параметром dwSize.
Например:
void CMyDlg::OnFileOpen() { // szFilters - строка с описанием и шаблоном фильтров char CMyDlg::szFilters[]= "Текстовые файлы (*.txt)|*.my|Все файлы(*.*)|*.*||"; // Создание диалога Open, использующего // для отображаемых файлов расширение txt CFileDialog fileDlg = new CFileDialog (TRUE, "txt", "*.txt", OFN_FILEMUSTEXIST| OFN_HIDEREADONLY, szFilters, this); if( fileDlg.DoModal ()==IDOK ) // Отображение диалога { CString pathName = fileDlg.GetPathName(); CString fileName = fileDlg.GetFileTitle (); } }
Отображение диалога стандартного Open также можно выполнить вызовом метода GetOpenFileName.
Например:
if (GetOpenFileName(&ofn)==TRUE) hf = CreateFile(ofn.lpstrFile, GENERIC_READ, 0, (LPSECURITY_ATTRIBUTES) NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL);
Классы диалогов библиотеки MFC
Библиотека MFC содержит большой набор классов, предназначенных для создания диалоговых окон. На следующей схеме приведена иерархия классов диалогов, наследуемых от класса CDialog.
MFC-приложения
MFC-приложения можно реализовывать как:
однодокументные приложения (Single document);многодокументные приложения (Multiple documents);приложения-диалоги (Dialog based).
Тип создаваемого приложения выбирается на странице Application type мастера создания MFC-приложения (рис. 19.1).
Рис. 19.1. Страница Application Тype мастера создания MFC-приложения
Черты пользовательского интерфейса для создаваемого диалога выбираются на страницах User Interface Features (рис.19.2) и Advanced Features (рис. 19.3) мастера создания MFC-приложения.
Рис. 19.2. Страница User Interface Features мастера создания MFC-приложения
Рис. 19.3. Страница Advanced Features мастера создания MFC-приложения
Приложения-диалоги
Базовым классом любого Windows-приложения, основанного на оконном интерфейсе, является класс cWinApp.
При создании приложения-диалога мастер построения MFC-приложения добавляет в проект два класса:
класс приложения, производный от cWinApp;класс диалога, производный от CDialog.
Класс приложения имеет следующее объявление и реализацию:
// Заголовочный файл class CD1App : public CWinApp {public: CD1App(); public: virtual BOOL InitInstance(); // Первый // выполняемый метод // Implementation DECLARE_MESSAGE_MAP() }; extern CD1App theApp; // Переменная - приложение // Файл реализации D1.cpp #include "stdafx.h" #include "D1.h" #include "D1Dlg.h" // CD1App BEGIN_MESSAGE_MAP(CD1App, CWinApp) // Обрабатываемые события ON_COMMAND(ID_HELP, &CWinApp::OnHelp) END_MESSAGE_MAP() CD1App::CD1App(){ } // Конструктор CD1App theApp; BOOL CD1App::InitInstance() { CWinApp::InitInstance(); AfxEnableControlContainer(); SetRegistryKey(_T("Local AppWizard-Generated Applications")); CD1Dlg dlg; // Создание объекта диалога m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); // Отображение // диалога if (nResponse == IDOK) { } else if (nResponse == IDCANCEL) { } return FALSE; // Завершение приложения }
Класс диалога имеет следующее объявление и реализацию:
// Заголовочный файл class CD1Dlg : public CDialog {public: CD1Dlg(CWnd* pParent = NULL); // Конструктор enum { IDD = IDD_D1_DIALOG }; // Ресурс диалога protected: // Поддержка DDX/DDV: virtual void DoDataExchange(CDataExchange* pDX); // Реализация protected: HICON m_hIcon;
// Функции таблицы обрабатываемых событий virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() }; // Файл реализации D1Dlg.cpp #include "stdafx.h" #include "D1.h" #include "D1Dlg.h" class CAboutDlg : public CDialog // Класс // вспомогательного диалога {public: CAboutDlg(); enum { IDD = IDD_ABOUTBOX }; // Ресурс диалога protected: virtual void DoDataExchange(CDataExchange* pDX); protected: DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX);} BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) END_MESSAGE_MAP() // Класс окна диалога приложения CD1Dlg::CD1Dlg(CWnd* pParent /*=NULL*/) : CDialog(CD1Dlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CD1Dlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CD1Dlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() END_MESSAGE_MAP() // Методы обработки событий BOOL CD1Dlg::OnInitDialog() { CDialog::OnInitDialog(); ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); // Указатель // на системное меню if (pSysMenu != NULL) // Добавление пунктов к { CString strAboutMenu; //системному меню strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } SetIcon(m_hIcon, TRUE); // Определение крупной // пиктограммы SetIcon(m_hIcon, FALSE); // Определение мелкой пиктограммы return TRUE; } void CD1Dlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } } void CD1Dlg::OnPaint() { CDialog::OnPaint(); }
HCURSOR CD1Dlg::OnQueryDragIcon() // Запрос курсора { return static_cast<HCURSOR>(m_hIcon); }
Листинг 19.1.
Для отображения окна диалога следует:
создать ресурс диалога, имеющий конкретный вид диалогового окна, и разместить в нем требуемые элементы управления (каждый элемент управления также определяется своим идентификатором ресурса);создать класс, наследуемый от класса CDialog (или производного от него), и связать создаваемый класс с ресурсом диалога.
Связывание командных кнопок с методами - обработчиками событий и идентификаторов ресурсов элементов управления с переменными выполняется в редакторе ресурсов.
Для назначения кнопке метода обработчика события можно выполнить на ней двойной щелчок мышью (для события BN_CLICKED) или выделить элемент управления и выполнить команду контекстного меню Add Event Handler.
По умолчанию для командной кнопки, выполняющей завершение диалога, будет вставлен следующий метод - обработчик события:
void CD1Dlg::OnBnClickedOk() { OnOK(); // OnOK - метод базового класса CDialog }
Имя метода - обработчика события формируется из префикса On, имени события и идентификатора элемента управления.
Обработчик события добавляется как новый член класса диалога.
При добавлении каждого нового метода обработчика события в таблицу сообщений также добавляется новый вход.
Таблица сообщений (иногда называемая также таблицей событий) указывается между макросами BEGIN_MESSAGE_MAP иEND_MESSAGE_MAP. Макрос BEGIN_MESSAGE_MAP имеет два параметра - имя класса, обрабатывающего данные сообщения, и имя базового класса. Чтобы определить, что данный класс имеет таблицу сообщений, в заголовочном файле указывается макрос DECLARE_MESSAGE_MAP.
Например:
BEGIN_MESSAGE_MAP(CD1Dlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDOK, &CD1Dlg::OnBnClickedOk) END_MESSAGE_MAP()
Каждый вход таблицы сообщений содержит имя события (например, ON_BN_CLICKED), идентификатор ресурса элемента управления (например, IDOK) и имя метода - обработчика события (например, &CD1Dlg::OnBnClickedOk).
Диалог может быть создан как:
модальный диалог, завершение которого необходимо выполнить до продолжения работы с приложением (диалог отображается вызовом метода DoModal);немодальный диалог, позволяющий получать одновременный доступ к другим немодальным диалогам данного приложения (диалог создается вызовом метода Create и сразу отображается, если установлен стиль диалога WS_VISIBLE).
Для того чтобы отобразить созданный диалог как модальный, следует создать объект диалога и выполнить для него метод DoModal().
Например:
CD1Dlg dlg; INT_PTR nResponse = dlg.DoModal();
Если предполагается определить диалог как главное окно приложения, то необходимо для объекта приложения установить значение свойства m_pMainWnd (CWnd* m_pMainWnd;).
Например:
CD1Dlg dlg; m_pMainWnd = &dlg; // AfxGetApp()->m_pMainWnd // AfxGetApp() возвращает для // приложения указатель на // объект типа CWinApp INT_PTR nResponse = dlg.DoModal();
Для модального диалога следует проверять код завершения диалога.
Например:
INT_PTR nResponse = dlg.DoModal(); if (nResponse == IDOK) { } else if (nResponse == IDCANCEL) { }
Для того чтобы отобразить диалог как немодальный, следует создать объект диалога и выполнить для него метод Create().
Например:
CMyDialog* pDialog; // Указатель на объект диалога void CMyWnd::OnSomeAction() { // pDialog может быть инициализирован как NULL // в конструкторе или классе CMyWnd pDialog = new CMyDialog(); if(pDialog != NULL) { BOOL ret = pDialog->Create(IDD_MYDIALOG,this); // Параметр // указывает используемый ресурс диалога if(!ret) AfxMessageBox("Ошибка при создании диалога"); pDialog->ShowWindow(SW_SHOW); } else AfxMessageBox("Ошибка с объектом диалога"); }
Создание приложений на С++ в Visual Studio .NET
Среда Visual Studio .NET позволяет создавать приложения с использованием различных библиотек:
библиотеки MFC для С++, предоставляющей широкий набор классов для создания SDI- и MDI-приложений, а также приложений, реализующих работу с базами данных, и серверных WEB-приложений;библиотеки активных шаблонов ATL;библиотеки .NET Framework, используемой при разработке приложений управляемого кода. Эта библиотека может применяться как для приложений управляемого кода на С++, так и для создания приложения на языках C# и Visual Basic.
Стандартные диалоги
Класс CCommonDialog - это базовый класс, инкапсулирующий поведение стандартных диалогов Windows. Библиотека MFC содержит классы стандартных диалогов, производных от CCommonDialog, включая следующие:
CFileDialog CFontDialog CColorDialog CPageSetupDialog CPrintDialog CPrintDialogEx CFindReplaceDialog COleDialog
Для использования класса CCommonDialog следует подключить заголовочный файл afxdlgs.h.