Пятница, 26.04.2024, 18:14
Приветствую Вас Гость | RSS

Лекции

Меню сайта
Форма входа
Категории раздела
ТАУ (Теория автоматического управления) [31]
лекции по ТАУ
Экология [151]
учебник
Бухгалтерский учет и налогообложение в строительстве [56]
Дементьев А.Ю. - Практическое пособие
Психология [104]
Пип
информатика [80]
с# Карли Ватсон
современные стулья [0]
новинки
Поиск

Главная » 2010 » Февраль » 11 » Нестандартные атрибуты
00:49
Нестандартные атрибуты
Нестандартные атрибуты
В первой половине главы мы сосредоточились на некоторых атрибутах, кото-
рые входят в состав .NET Framework. Но это еще не все — можно создавать свои
собственные атрибуты.
В этой главе мы только обрисуем в общих чертах те возможности, которые
таят в себе нестандартные атрибуты. В этом разделе мы познакомимся с разраба-
тываемыми атрибутами:
• TestcaseAttribute позволяет привязать код, в котором осуществляется
тестирование некоторого класса, к самому классу
• BugFixAttribute позволяет сохранять информацию о том,
кто изменял что-либо в исходном коде и когда это делалось
• DatabaseTableAttribute демонстрирует, как МОЖНО Создавать
схему базы данных из классов .NET.
Атрибуты 631
Нестандартный атрибут — это просто специальный класс, который должен
удовлетворять следующим двум требованиям:
• Атрибут ДОЛЖен быть ПРОИЗВОДНЫМ ОТ System. Attribute
• Конструктор(ы) атрибута может содержать в себе только типы,
которые могут быть найдены в процессе компиляции,
например, string И integer
Эти ограничения, налагаемые на типы, которые могут содержаться в конструкторе
(конструкторах) атрибута, возникают вследствие способа, используемого для хра-
нения атрибутов в метаданных модуля. Когда вы применяете атрибут внутри неко-
торой программы, то вы, фактически, используете конструктор атрибута. Например:
[assembly: AssemblyKeyFile ("Company.Public") ]
Этот атрибут хранится в метаданных модуля в виде инструкции, вызывающей
конструктор атрибута AssembiyKeyFiieAttribute, которому в качестве параметра
передается строка. В приведенном выше примере это строка -company.Public.
Если мы определяем нестандартный атрибут, то пользователи данного атрибута
обычно записывают параметры в конструктор класса.
Рассмотрим в качестве первого примера атрибут TestcaseAttribute, который
показывает, каким образом классы, предназначенные для тестирования могут быть
привязаны к тестируемому ими коду.
Атрибут TestcaseAttribute
В программном обеспечении, предназначенном для тестирования, распростра-
ненной практикой является определение множества тестовых классов, использую-
щих тестируемый класс (классы), чтобы убедиться в том, что он функционирует
надлежащим образом. Это особенно справедливо в случае регрессионного тести-
рования, когда исправляется некоторая ошибка или добавляется какая-либо новая
функциональная возможность и необходимо убедиться в том, что вы ничего не на-
рушили.
При работе с заказчиками, деятельность которых каким-либо образом регули-
руется (например, при создании программного обеспечения для фармацевтических
компаний, которые работают под жестким контролем государственных агентств),
необходимо создавать перекрестные ссылки между основным и трестирующим
программным обеспечением. Описываемый в разделе атрибут TestcaseAttribute
может оказаться полезным для отслеживания связей между неким классом и клас-
сом, предназначенным для его тестирования.
Практикум: создание класса нестандартного атрибута
ПОЛНОСТЬЮ КОД ДанНОГО примера МОЖНО наЙТИ В Директории Chapter22/TestCase.
Для создания класса нестандартного атрибута, требуется выполнить следующие
действия:
• Создать КЛаСС, ПРОИЗВОДНЫЙ ОТ System.Attribute
• Создать необходимые конструктор(ы) и общие свойства
• Включить в этот класс атрибут, который позволит определить,
в каких случаях допустимо использовать нестандартный атрибут.
632 Глава 22
1. Создание класса нестандартного атрибута
Этот шаг оказывается наиболее простым. Все что требуется дая его выполне-
ния — ЭТО СОЗДаТЬ КЛаСС, ПРОИЗВОДНЫЙ ОТ Класса System. Attribute!
. . ; ; . ; • : ; : ; ; : • - • • ; . : • •• •
I • • ' •.,.;:;Р--::;";:;:--.' ' ' ' ' • ; ": 'Ш;
ШЩШт •:Ш.: • :Ш :;•:••::-":;Ш: У: Ш^''^ • :'' "• •• ":•;: -,::''' ^ : ^ : ?ШШШ^:: : - : •:: - • - : ':' • • • " :' й й Ш '
2. Создание конструкторов и свойств
Как отмечалось выше, при использовании атрибута происходит вызов конструк-
тора атрибута. В данном случае нужно определить тип объекта, используемого для
проверки класса. Это будет system.туре:
public class TestCaseAttribute : Attribute
ттш шшшт. { - * . ^^
/// <summary>
/// Конструктор класса
/// </summary> ; .
/// <param name="testCase'^Объект, который содержит в себе
/// вариант тестирования</рагаш> . : •:
public TestCaseAttribute (System.Type testCase)
{
TestCase = testCase;
ШШШШШ ••.>•••'' '•••*
lif <summary>
/// Выполнение тестирования
///</summary>
punlic readonly System.Type testCase)
/// <summary> ',•• ; :
/// Проверочный класс
•i: ' ;;:: ... /// < / s u m m a r y >
public void Test ()
{ • . - ; ,./:-:;- '.
// Создание экземпляра тестируемого класса
// Предполагается, что созданный объект с данным вариантом
// тестирования будет использоваться для тестирования
//в конструкторе объекта
object о = Activator.Createlnstance (TestCase);
%::::'::::-l'?-::^:S Vi^J^i^Si
}
В данной программе описывается единственный конструктор и доступный в режи-
ме только чтение член TestCase. Метод Test о используется для создания экземп-
ляра с вариантом тестирования, поскольку в данном случае тестирование будет
выполняться внутри конструктора тестирующего класса.
3. Включение атрибута в класс для его использования
Последнее, что необходимо сделать,— это включить атрибут в класс, что по-
зволит указать область применеия этого класса атрибута. Мы хотим объявить, что
атрибут варианта тестирования допустимо использовать только в классах. Мы
имеем возможность самостоятельно определять, где допустимо использовать со-
здаваемые нами атрибуты. Подробнее этот вопрос будет рассматриваться ниже:
[AttrributeUsage(AttributeTargets.Class,
AllowMultiple=false,
Inherited-true)]
Атрибуты _____^_ 633
public class TestCaseAttribute : Attribute
{
/// <summary>
/// Конструктор класса
/// </summary>
/// <param name="testCase">Объект, который содержит в себе
/// вариант тестирования</рагат>
public TestCaseAttribute ( System.Type testCase )
{
TestCase = testCase;
/// <summary>
/// Проверочный класс
/// </summary>
public readonly SystemType. TestCase:
/// <summary>
/// Выполнение тестирования
/// </summary>
public void Test ( )
{
// Создание экземпляра тестируемого класса
// Предполагается, что созданный объект с данным вариантом
// тестирования будет использоваться для тестирования
//в конструкторе объекта
object о = Activator.Createlnstance (TestCase);
Атрибут AttrributeUsage обладает единственным конструктором, которому пе-
редается значение перечислимого типа AttributeTargets (будет полностью описа-
но в этом разделе ниже). В данном случае мы утверждаем, что единственным
допустимым местом для размещения атрибута TestCase является класс. В этом пе-
речислимом типе можно задавать несколько различных значений с помощью клю-
чевого слова OR — т. е. допускается использовать другие атрибуты либо в классах,
либо в конструкторах, либо в свойствах.
В определении атрибута были использованы и его свойства — AiiowMuitiple
и inherited. Эти свойства также будут обсуждаться более подробно в настоящем
разделе ниже.
Теперь нам потребуется объект, который будет тестироваться с помощью дан-
ного варианта тестирования. В этом классе нет ничего загадочного:
[TestCase (typeof(TestAnObject))]
public class SomeCodeOrOther
{ . ' . : • ' .
public SomeCodeOrOther ()
public int Do ()
{
return 999 ;
Для этого класса в качестве префикса используется атрибут TestCase, а далее
применяется функция typeof о для описания класса, который будет использовать-
ся для тестирования рассматриваемого кода. В данном примере не хватает класса,
634 Глава 22
осуществляющего тестирование. Ниже приводится объект, используемый для про-
верки экземпляра кода, который подвергается тестированию:
public class TestAnObject
{ •:" " .'•;
public TestAnObject () • . •••:,•• •
{ \ . . ;• • , . \ •/'••
// Проверка тестируемого класса
SomeCodeOrOther scooby = new SqmeCodeOrQther () ;
if (scooby.Do () ! = 999)
throw new Exception {*Pesky Kids") ; ;
Данный класс просто создает экземпляр тестируемого класса, вызывает метод
и генерирует исключительную ситуацию, если возвращенное значение отличается
от ожидаемого. Более развернутый вариант тестирования мог бы осуществлять про-
верку тестируемого объекта более полно, вызывая все методы, которыми обладает
данный класс, передавая им значения, выходящие за пределы допустимого для про-
верки условий возникновения ошибок, и, возможно, создавая какие-либо другие
классы в зависимости от текущей информации; например, если тестируемый класс
осуществляет доступ к базе данных, то вы, возможно, захотите передать ему в ка-
честве объекта соединение с базой данных.
Перейдем к основному коду. Этот класс в цикле просматривает все типы, опре-
деленные в модуле, пытаясь обнаружить те их них, где есть описание атрибута
TestCaseAttribute. В случае обнаружения такого описания атрибут извлекается,
и вызывается метод Test ():
using Systemsusing
System.Reflection ;
[AttributeUsage(AttributeTargets.Class/AllowMultiple=false/Inherited=true)]
public class TestCaseAttribute : Attribute
{
// Код, пропущенный для краткости
}
/// <summary>
/// Класс, в котором используется атрибут TestCase
/// </summary>
[TestCaseAttribute(typeof(TestAnObject))]
public class SomeCodeOrOther
{
// Код, пропущенный для краткости
}
// Основной класс программы
public class UnitTesting ( )
{ •••
public static void Main()
•• { - '":'••
II Поиск всех классов, в которых содержатся варианты тестирования
// в данном модуле
Assembly a = Assembly.GetExecutingAssembly () ;
// Проход в цикле по всем типам, описанным а модуле, и их тестирование
//в случае необходимости
System.Type[] types = a.GetExportedTypes () ;
Атрибуты 635
: foreach (System.Type t in types)
{
// Вывод имени типа. ..
Console.WriteLine {"Checking type {0}* , t.ToString ()) ;
// Включает ли данный тип нестандартный атрибут TestCaseAttribute?
object[] atts = t.GetCustomAttributes(typeof(TestCaseAttribute),
• • . ' ' . , ••• , : • : , : ' Л ' - ' ' ' : : • ••••••.• ' '•: ' • '• ' '. ' ' ' - : . ' " ' . ' • • f a l s e ) •; • • . - , • • • \ ' ,
if (1 == atts.Length)
Console.WriteLine (* Found TestCaseAttribute: Running Tests") ;
*> // OK, данный класс обладает вариантом тестирования. Запускаем его. ..
TestCaseAttribute tea = atts[0] as TestCaseAttribute;
try
{ ' .;'. • './... ;
//Осуществляем тестирование. ..
tea.Test () ;
Console.WriteLine (* PASSED!*};
}
catch (Exception ex)
{
Console.WriteLine (• FAILEDi");
Console.WriteLine (ex.ToString ());
} • ••:. Ш -Ш ШШ
Новый участок кода выделен серым цветом. В начале работы программа получает
ВЫПОЛНЯЮЩИЙСЯ МОДУЛЬ ПОСреДСТВОМ СТаТИЧеСКОГО Метода GetExecutingAssemblyO
класса Assembly. Далее вызывается метод GetExportedTypes для определения всех
типов объектов, которые являются общедоступными в данном модуле.
Для каждого экспортируемого в данный модуль типа проверяется наличие в нем
атрибута Testcase. Если такой атрибут существует, он извлекается (при этом
внутри него создается экземпляр атрибута путем передачи параметров, которые
внутри кода передаются конструктору объекта) и происходит вызов метода Test о ,
который и производит тестирование кода.
Если эту программу запустить, то будет выведено следующее:
Checking type TestCaseAttrubute
(Проверка типа TestCaseAttrubute)
Checking type SomeCodeOrOther
(Проверка типа SomeCodeOrOther)
Found TestCaseAttrubute: Running Tests
(Обнаружен TestCaseAttrubute: Запуск тестирования)
PASSED!
(Тесты пройдены!)
Checking type TestAnObject
(Проверка типа TestAnObject)
Checking type UnitTesting
(Проверка типа TestCaseAttrubute)
636 Глава 22
Атрибут System.AttributeUsageAttribute
При определении классов нестандартных атрибутов необходимо определять
также тип (типы), где данный атрибут может быть использован. В предыдущем
примере использование атрибута Testcase был определено только для классов.
Для того чтобы определить, где использование данного атрибута является допусти-
мым, Следует ВОСПОЛЬЗОВаТЬСЯ еще ОДНИМ атрибутом — AttributeUsage.
Ниже приведена наиболее простая форма использования этого атрибута:
[AttributeUsage(AttributeTargets.Class)3
В качестве единственного параметра используется перечислимый тип, указыва-
ющий, где допустимо использовать данный атрибут. Если попытаться использовать
атрибут Testcase для какого-либо метода, компилятор выдаст сообщение об ошиб-
ке. Вот один из вариантов недопустимого использования этого атрибута:
public class TestAnObject
{
[TestCase (typeof{System.String))] // Недопустимое использование
public TestAnObject ()
{
и т. д. ... ... .
Это приведет к получению следующего сообщения об ошибке:
TestCase.cs(54,4):error CS0592: Attribute TestCase' is not valid on this declaration type.
It is valid on 'class' declarations only.
(TestCase.сз(54,4):ошибка CS0592: Использование атрибута TestCase' недопустимо
для данного типа объявления. Он может использоваться только с объявлениями 'class'.)
В перечислимом типе AttributeTargets описаны следующие члены, которые
могут использоваться в различных сочетаниях, объединенных с помощью операто-
ра (|), что позволяет определить то множество элементов, в которых допускается
использование данного атрибута:
Значение Описание
перечислимого типа
AttributeTargets
All Является допустимым в любом месте модуля.
Assembly Является допустимым для модуля. В качестве примера можно привести
атрибут AssemblyKeyFile, использовавшийся в начале главы.
Class Является допустимым для использования в определении класса.
Это значение использовалось для атрибута TestCase.
Другим примером может служить атрибут S e r i a l i z a b l e .
Constructor Является допустимым только в конструкторах классов.
Delegate Является допустимым только в делегатах.
Ennm Может добавляться в описание перечислимого типа. Одним из примеров
такого атрибута является атрибут System.FlagsAttribute, который, будучи
примененным к некоторому перечислимому типу, определяет, что пользователь
имеет возможность использовать побитовый оператор ог для объединения
различных значений этого перечислимого типа. В перечислимом типе
A t t r i b u t e T a r g e t s этот атрибут также используется.
Event Является допустимым при определении событий.
Атрибуты
Значение
перечислимого типа
AttributeTargets
Описание
637
Продолжение таблицы
Field
Interface
Method
Module
Parameter
Property
ReturnValue
Struct
Может быть размещен в некотором поле, например, во внутренней переменной.
В качестве примера можно привести атрибут NonSerialized, который
использовался нами ранее для объявления того, что некоторое конкретное
значение не должно сохраняться при сериализации класса.
Является допустимым для интерфейса. Одним из примеров такого атрибута
может служить атрибут GuidAttribute, описанный в пространстве имен
System.Runtime.InteropServices, который позволяет явно определять GUID
для данного интерфейса.
Определяет, что атрибут может использоваться для метода. Это значение
используется, например, атрибутом Oneway, описанном в пространстве имен
System.Runtime.Remoting.Messaging.
Определяет, что атрибут допустимо использовать в отсеке. Модуль может
состоять из нескольких отсеков с кодом; таким образом, этот атрибут позволяет
размещать атрибут в отдельном отсеке, а не во всем модуле.
Может применяться для некоторого параметра в описании метода.
Может быть применен к свойству.
Может быть привязан к возвращаемому значению функции.
Является допустимым в определении структур.
Область действия атрибута
В первом примере, приведенном в настоящей главе, нам встретились атрибуты
Assembly*, синтаксис которых очень напоминает приведенный ниже:
[assembly: AssemblyTitle("Wrox rocks")]
Строка assembly: определяет область действия атрибута, которая в данном слу-
чае служит для передачи компилятору информации о том, что атрибут Assembly-
Title должен применяться к самому модулю. Модификатор области действия
должен применяться только в тех случаях, когда компилятор оказывается не в со-
стоянии самостоятельно определить эту область действия. Допустим, что вы хотите
включить атрибут в значение, возвращаемое следующей функцией:
[MyAttribute О]
public long DoSomething ()
Когда компилятор дойдет до этого атрибута, он примет вполне обоснованное
предположение о том, что атрибут применяется ко всему методу, а это совсем не
то, что требуется в данном случае, поэтому следует воспользоваться модификато-
ром области действия, для того чтобы явно указать, к чему данный атрибут отно-
сится:
[return:MyAttribute {)]
public long DoSomething ()
638 ] Глава 22
Для определения области действия атрибута следует использовать одно из при-
веденных ниже значений:
• assembly — атрибут применим ко всему модулю
Q field — атрибут применим к полю перечислимого типа или класса
• event — атрибут применим к событию
• method — атрибут применим к методу, описанию которого
он предшествует
• module — атрибут хранится в отсеке
• param — атрибут применим к параметру
• property — атрибут хранится для данного свойства
• return — атрибут применим к возвращаемому функцией значению
• type — атрибут применим к классу, интерфейсу или структуре.
Большинство из этих значений используются очень редко, поскольку область
действия обычно определяется однозначно. Однако значения assembly, module
и return часто приходится использовать для определения области действия. Если
есть какая-либо неоднозначность, к чему относится данный атрибут, то компиля-
тор самостоятельно выбирает тот объект, к которому этот атрибут будет приписан.
Наиболее распространенным случаем является использование атрибута для воз-
вращаемого значения функции, как показано ниже:
[SomeAttribute]
public string DoSomething ();
В данном случае компилятор воспримет этот атрибут как относящийся ко всему
методу, а не к возвращаемому значению. Для того чтобы добиться желаемого эф-
фекта, необходимо описать область действия атрибута следующим образом:
[return:SomeAttribute]
public string DoSomething () ;
Атрибут AttributeUsage.AllowMultiple
С помощью этого атрибута мы определяем, может ли пользователь включать
один или несколько одинаковых атрибутов в некоторый элемент. Например, можно
создать атрибут, в котором будет храниться перечень всех исправленных ошибок,
применимый к некоторому участку кода. При создании модуля может появиться
необходимость включить в него подробное описание нескольких обнаруженных
ошибок.
Атрибут BugFixAttribute
В приведенном ниже коде определяется простой атрибут BugFixAttribute и ис-
пользуется флажок AiiowMuitiple с тем, чтобы данный атрибут мог быть исполь-
зован более одного раза для некоторого участка кода:
[AttributeUsage (AttributeTargets.Class I AttributeTargets.Property I
AttributeTargets.Method I AttributeTargets.Constructor ,
AllowMultipleatrue)]
public class BugFixAttribute : Attribute
{ . ' ;: . - • • • • ' ; / ; ; ; : :
public BugFixAttribute (string bugNumber , string comments)
Атрибуты 639
BugNumber = bugNumber;
public readonly string BugNumber;
}
Конструктор атрибута BugFix принимает номер ошибки и строку комментария,
при этом он помечен флажком AiiowMuitipie=true, откуда следует, что он может
быть использован, например, таким образом:
[BugFixC'lOl","Created some methods")]
public class MyBuggyCode
^МШ • { ' ' : •
£BugF±x("90125H,"Removed call to base{)M)3
- шШй
[BugFix\"2112","Returned a non null string")]
[BugFix("38382","Returned OKIS)J
ШШ.^^гШ^'Ш- ' : : :^:;v ^'^ ••./'••;,M.^ •:^;г:::?::;^-:^Я':;::^:--:'^Ш^
• " : • { : ' Л ' .
return "OK" ;
Синтаксис, использованный для присваивания значения флажку AiiowMuitiple,
имеет несколько странный вид. Конструктор атрибута AttributeUsage принимает
только один параметр — перечень флажков, определяющих область применения
ДаННОГО атрибута. AiiowMuitiple ЯВЛЯеТСЯ СВОЙСТВОМ атрибута AttributeUsage,
следовательно приведенный ниже синтаксис означает "создай атрибут, а затем
присвой его свойству AiiowMuitiple значение true":
[AttributeUsage (AttributeTargets.Class I AttributeTargets.Property I
AttributeTargets.Method. ! AttributeTargets.Constructor ,
AllowMultiple=true)] ..
public class BugFixAttribute : Attribute
Схожий метод используется и для свойства inherited, которое обсуждается
ниже в настоящей главе. Если нестандартный атрибут обладает какими-либо свой-
ствами, то им можно присваивать значения аналогичным способом. Примером мо-
жет служить добавление фамилии человека, исправившего данную ошибку:
public readonly string BugNumber;
public readonly string Comments;
public string Author = null; :• • • > V
public override string ToString ()
{
if (null == Author) . •••:.• "
return string.Format {*BugFix {0} : {1}" ,
BugNumber , Comments) ;
else
return string.Format (-BugFix {0} by {1} : {2}w ,
BugNumber , Author , Comments) ;
640 ___ Глава 22
В результате этого мы добавляем свойство Author (автор) и переопределенную
реализацию метода TostringO, которая позволяет выводить подробную информа-
цию об ошибке, если свойству Author присвоено какое-либо значение, и только
номер ошибки и комментарии к ней в противном случае. Метод TostringO может
быть использован для вывода перечня исправленных ошибок для данного участка
кода — например, распечатан и сохранен в файле.
После того как создан атрибут BugFix, вам потребуется некий способ, позволя-
ющий сообщать об исправленных ошибках в данном классе и членах данного класса.
Этот способ заключается в передаче типа класса (снова System.туре) функции
DispiayFixes, приведенной ниже. В ней также используется отражение для поиска
всех исправленных ошибок для данного класса, а затем эта функция проходит по
всем методам этого класса в поисках атрибутов исправленных ошибок.
ЭТОТ Пример МОЖеТ быть найден В Директории Chapter22 /BugFix:
public static void DispiayFixes (System.Type t)
{
// Получение всех исправленных ошибок для данного типа;
//в данном случае предполагается что это класс
object[] fixes = t.GetCustomAttributes (typeof {BugFixAttribute) , false);
Console.WriteLine ("Displaying fixes for {0}" , t);
//Вывод информации об исправленной ошибке *
foreach (BugFixAttribute bugFix in fixes)
Console.WriteLine {" {0}" , bugFix);
/У Теперь требуется получить все члены (т. е. функции) данного класса
foreach (Memberlnfo member in t.GetMembers (BindingFlags.Instance I
• . BindingFlags.Public I
BindingFlags.NonPublic I
BindingFlags.Static ))
{
: // И отыскать все исправленные ошибки и в них также
object[] memberFixes = member.GetCustomAttributes (typeof (BugFixAttribute) ,
false) ;
if (memberFixes.Length > 0)
" . • • • • ' , ., . ' Л / • ( ' . "•":?— •' • • • • • . ' , - •
. Console.WriteLine С {0} " , member .Name) ;
- //Проход в цикле и вывод этих ошибок
foreach {BugFixAttribute memberFix in memberFixes)
Console.WriteLine (* {-0}.* , memberFix) ;
Как это работает
Первое, что осуществляется в данном коде,— это извлечение всех атрибутов
BugFix, относящихся к самому типу:
object[] fixes = t.GetCustomAttributes (typeof (BugFixAttribute) , false);
Эти атрибуты нумеруются и выводятся. Далее программа проходит по всем опи-
санным членам данного класса посредством использования метода GetMembers ():
foreach (Memberlnfo member in t.GetMembers (
BindingFlags.Instance I BindingFlags.Public I
BindingFlags.NonPublic I BindingFlags.Static))
Атрибуты 641
Метод GetMembers () позволяет извлекать свойства, методы и поля, описанные
в данном типе. Для того чтобы ограничить перечень возвращаемых членов, при-
меняется перечислимый тип BindingFiags (который описан в пространстве имен
System. Reflection).
Флажки привязки, передаваемые этому методу, позволяют указать, какие именно
члены нас интересуют. В данном случае представляют интерес все переменные —
как статические, так и переменные экземпляра, независимо от того, являются ли
они видимыми или нет, поэтому мы задаем значения instance и s t a t i c вместе со
Значениями Public И NonPublic.
Получив все интересующие нас члены, мы проходим по ним в цикле, находя все
атрибуты BugFix, привязанные к данному члену, и выводим их на консоль. Для
того чтобы вывести перечень исправленных ошибок для данного класса, все, что
требуется,— это обратиться к статическому методу DispiayFixesO, передав ему
тип класса:
BugFixAttribute.DisplayFixes (typeof (MyBuggyCode))'
Для класса MyBuggyCode, приведенного ранее, это позволит получить следую-
щий выходной поток:
Displaying fixes for MyBuggyCode /Вывод исправлений для класса MyBuggyCode/
BugFix 101 : Created some methods /Создание некоторых методов/
DoSomething
BugFix 2112 : Returned a non null string /Возврат ненулевой строки/
BugFix 38382 : Returned OK /Возврат OK/
.ctor
BugFix 90125 : Removed call to base() /Удаление обращения к базе данных/
Если требуется вывести исправления для всех классов данного модуля, то можно
воспользоваться отражением для получения всех типов, имеющихся в модуле, а за-
тем передать каждый ИЗ НИХ статическому методу BugFixAttribute.DisplayFixes.
Свойство AttributeUsageJnherited
Любой атрибут можно объявить как наследуемый, присвоив этому флажку зна-
чение true при определении нестандартного атрибута:
[AttributeUsage (AttributeTargets.Class, •
Inherited = true) ]
public class BugFixAttribute :{...}
Это позволяет обозначить тот факт, что атрибут BugFix будет наследоваться
всеми подклассами класса, в котором он используется, что, на самом деле, может
быть в одних случаях желательно, а в других — нет. Для атрибута BugFix такое
поведение, скорее всего, будет нежелательным, поскольку исправленная ошибка
обычно относится только к самому классу, а не к производным от него классам.
Предположим, что имеется следующий абстрактный класс, к которому приме-
няется данный атрибут:
[BugFix ("38383", 'Made the class abstract11)]
public'abstract class GenericRow : DataRow
{
public GenericRow (System.Data.DataRowBuilder builder)
: base (builder)
642 Глава 22
Если мы создадим подкласс данного класса, то, скорее всего, не захотим, чтобы
тот же самый атрибут BugFix был отражен в этом подклассе, поскольку такая
ошибка в этом подклассе не исправлялась. Однако если бы мы описывали семей-
ство атрибутов, которые привязывают члены класса к полям таблицы в базе дан-
ных, то в этом случае, возможно, потребовалось бы наследование этих атрибутов.
Чрезвычайно распространенной является практика, когда при описании схемы
базы данных используется набор стандартных столбцов, который входит в состав
большинства таблиц, таких, например, как Name (имя) или Description (описание).
Можно использовать эти поля при кодировании базового класса, а затем включить
в него нестандартный атрибут, который привяжет свойство, описанное в классе,
к соответствующему столбцу в базе данных. В других подклассах при необходимо-
сти могут использоваться дополнительные поля.
В Следующем Примере МЫ создадим атрибуты DatabaseTable И DatabaseColumn,
которые могут быть применены для класса таким образом, чтобы стало возможно
автоматическое создание таблицы базы данных, пригодной для хранения данного
класса.
Практикум: создание таблиц баз данных с использованием атрибутов
Последний пример данной главы позволит продемонстрировать, каким образом
атрибуты могут быть использованы в классах .NET для создания схемы базы дан-
ных — структуры базы данных, состоящей из таблиц, столбцов и типов данных,
которая позволила бы объектам .NET создавать свои собственные таблицы базы
данных, где могли бы храниться эти объекты. Мы познакомимся с тем, как извле-
кать информацию об этой схеме для создания SQL-запроса, позволяющего созда-
вать таблицы в базе данных, а также создавать объекты DataTabie, хранящиеся
в памяти.
Как стало ИЗВеСТНО ИЗ главы 19, объекты DataSet, DataTabie, DataRow И Data-
Adapter используются для осуществления доступа к данным в ADO.NET. Пред-
ставляется важным использовать эти объекты в соответствии со структурой, на
которой основывается база данных. Если в БД со временем вносятся какие-либо
изменения, то нужны гарантии того, что эти изменения, например, включение но-
вых столбцов, будут отражены в классах, ответственных за организацию доступа
к этой базе данных.
В данном пример мы создадим подклассы класса DataRow, предназначенные
специально для хранения данных из конкретной таблицы базы данных. Когда осно-
вополагающая схема базы данных изменяется не слишком часто, такой подход по-
зволяет обеспечить весьма эффективный способ организации доступа к БД. Если
же схема базы данных изменяется достаточно часто или если вы позволяете поль-
зователям самостоятельно вносить изменения в структуру базы данных, то наиболее
правильным подходом будет динамическое создание таблиц данных посредством
запроса в соответствующей базе данных
информации о схеме и построение таб-
лиц данных по ходу работы.
На диаграмме (см. рис. слева) проил-
люстрированы отношения между классами
ADO.NET и лежащей в их основе таб-
лицей базы данных.
Класс DataSet СОСТОИТ ИЗ ОДНОГО ИЛИ
неСКОЛЬКИХ объектов DataTabie, каждый
ИЗ КОТОРЫХ Обладает Объектами DataRow,
DataSet
DataTabie
DataRow
•-i; 1 п.,! Л1Л.1, . .... jU»
— — - ^
A U T H O R

•ре л.
AUTHOR ID bigtint
NAME varchar
DESCRIPTION varchar
HIRE DATE datebme
DataAdapter
Атрибуты 643
[DatabaseTable("AUTHOR")J
public class AuthorRow: DataRow
[DatabaseColumn("AUTHOR_ID")]
public long AuthorlD
{
[DatabaseColumnfNAME")]
public string Name
{
[DatabaseColumn("DESCRIPTION")]
public string Description
[DatabaseColumn("HIRE_DATE")]
public DateTime HireDate
AUTHOR JD bight
NAME varchar
DESCRIPTION varchar
HIRE DATE datetme
которые соответствуют отдельной строке из таблицы БД. Класс DataAdapter ис-
пользуется для извлечения данных из базы данных и переноса этих данных в объ-
ект DataTabie, а также для записи данных из объекта DataTabie обратно в базу
данных.
М ы определим базовый объект
класса DataTabie, КОТОрыЙ будет
служить универсальным контей-
нером Объектов DataRow. Объекту
же DataRow, однако, необходимо
обеспечить корректный с точки
зрения типов доступ к столбцам
базы данных, поэтому разобьем
его на подклассы. Отношения
между этим объектом и лежащей
в его основе таблицей, изображе-
ны на рисунке слева.
Основное внимание здесь будет
уделено таблицам Books И Authors.
Данный пример состоит только из
этих двух таблиц, которые будут демонстрироваться на протяжении нескольких
следующих страниц. Хотя данный пример разработан для работы с SQL Server, вы
можете внести в него изменения, которые позволят вам работать с Oracle или
любой другой базой данных.
Класс AuthorRow является производным от класса DataRow и включает в себя
свойства всех столбцов лежащей в его основе таблицы Author. К классу DataRow
был добавлен атрибут DatabaseTabie, и теперь для каждого свойства, относящегося
К КОНКретНОМу СТОЛбпу В Данной таблице, Существует СВОЙ атрибут DatabaseColuinn.
Некоторые параметры этих атрибутов были удалены, чтобы изображение могло
уместиться на экране. Со всеми подробностями мы познакомимся в последующих
разделах.
Атрибут DatabaseTabie
Первый атрибут, который будет использоваться в данном примере, предназна-
чается для передачи информации классу — в данном случае DataRow — об имени
таблицы базы данных, в которой будет сохраняться объект DataRow. Код данного
примера МОЖет быть найден В директории Chapter22/DatabaseAttributes:
// Извлечение из файла DatabaseAttributes.es .......
/// <summary>
/// Атрибут, который используется в данном классе для определения
[AttributeUsage (AttributeTargets.Class , Inherited = false ,
AllowMultiple=false)]
public class DatabaseTableAttribute : Attribute
/// <summary>
•• шШтШШ • .'
• : /// </summary>
/// <param name=ntableName">Ha3BaHMe таблицы базы данных</рагат>
public DatabaseTableAttribute (string tableName)

644 Глава 22
III <summary>
//./ Возвращает название таблицы базы данных
/// </summary>
public readonly string TableName;
}
Данный атрибут состоит из конструктора, которому передается имя таблицы
В ВИДе СТрОКИ, И Обладает модификаторами Inherited=false И AllowMultiple=false.
Маловероятно, что вы пожелаете, чтобы этот атрибут наследовался какими-либо
подклассами, а множественное использование атрибутов запрещено по той про-
стой причине, что данный класс ссылается только одну таблицу.
Внутри класса атрибута название таблицы хранится в виде поля, а не в виде
свойства. Это вопрос личного выбора. В данном случае не предусмотрено никакого
метода, позволяющего изменять значение поля, в котором хранится название таб-
лицы, поэтому наиболее подходящим является поле, доступное в режиме "только
чтение". Если вам больше нравится использовать свойства, то вы можете внести
в исходный код соответствующие изменения.
Атрибут DatabaseColumn
Данный атрибут предназначен для использования с общими свойствами класса
DataRow, он применяется, когда нужно определить названия столбца, к которому
данное свойство будет привязано, а также, может ли в данном столбце хранится
значение null:
// Извлечение из файла DatabaseAttributes.cs
/// <summary>
/// Этот атрибут используется для всех свойств, которые :
///представляют столбцы базы данных
.•:: • .. ///• < / s u m m a r y > • • • • . . ••• •; •
[AttributeUsage (AttributeTargets.Property , Inherited^true ,
AllowMultiple=false)]
public class DatabaseColumnAttribute : Attribute
{
• / / / <SUinmary> , •• , . • • • ••,...:••,••• . : : . _ ^ ' , . . . • . , , . • : . • • • . : . ' •'
III Создание атрибута столбца базы данных
... /// </summary > ;
/// <param name="column">Название столбца</рагат>
/// <param name =иdataType">Tnn данных, хранящихся в данном столбце</рагат>
public DatabaseColumnAttribute (string column , ColumnDomain dataType)
Ч " " ' • : • • { -.;:;• ../.". . ;,::^:,S;'r.v;..:::-..: . • '
ColumnName « column;
DataType = dataType;
Order = GenerateOrderNumber () ;
/// <summary>
///Возвращает название столбца
/// </summary>
public readonly string ColumnName;
/// <summary> t • ^W:;
:::';:V:;;-;:• .-: •
/// Возвращает домен столбца • •
• • • • : . , /// </summary> ' . . • • ; • •
public readonly ColumnDomain DataType;
/// <summary>
/// Считывает/записывает значение флажка Nullable. Возможно,
/// лучше использовать свойство
• • . • ' , • : / / / < / S U m m a r y > . - • • . : •/• • " • •••••• •• :•. • : - : : , - , •• ••••,: ,. . • . : • ' .' . •. ' . ' .'. . , ' ' •' ' . , • / • • : . •. ••••:•..• :/.,•••••:••; :•
public bool Nullable = false;
/// <summary>
Атрибуты 645
III Считывает/записывает значение порядкового номера Order. В этом
/// случае использование свойства также может оказаться более приемлемым.
•••/// </summary>
public int Order;
•••••III < s u m m a r y >
///Считывает/записывает значение Size, представляющее размер столбца
/// (полезно для столбцов с текстом).
• ':'. • : • • //I <lsummary> • . •: . •• , • :. •••••••. . • , : . . - • ; : . •.'
public int Size;
III <summary>
/У/ Генерирует возрастающую последовательность порядковых номеров
/// для столбцов
/// </summary>
III <returnsx/returns>
public static int GenerateOrderNumber ()
{
return nextOrder++;
\ •••••'• •/•/•/ < s u i r a n a r y > ••••• , . . • .'. :'•' :: . , " • . . . . ' • • • ' ] ' . : . ' : ' . .
/// Частное значение, используемое при генерировании порядковых номеров
: : /// </summary> . •
private static int nextOrder = 100; : ,
Ill <summary>
///Пронумерованный список типов данных в столбцах
•/// </summary>
public enum Columnpornain
{
.-•/•/У <summary>
/// 32 разряда
/// </summary>
Integer,
• ••••••/У/ < s u m m a r y >
/// 64 разряда
III </summary>
• L o n g , - • У • :... • '.-.••:
/// <summary>
/// Столбец, содержащий строку
•/.././• < / s u m m a r y > :
, • • Л . , : •• " •• • •• ••••••••. S t r i n g , :• • :• • . ' ' ' , :• • ' • . '. .. . ' .- ' ' ' ' •'' ,•' V . : '. . : ; .•
/// <summary>
///Столбец, содержащий дату и время
. . • ' . , •, ' : :• /// </suiranary> С.\. :. ,'.•••. • ' " ' • : ; .• : .. : ' .. ••• ' : ' •••:•••••.:/• ••..:••..••;•••. . • . ••
DateTime
} ill!
Данный класс также помечен флажком AilowMuitipie=faise, поскольку в дан-
ном случае также имеется соответствие один к одному между свойством DataRow
и столбцом, к которому оно привязано.
Мы обозначили этот атрибут как наследуемый, с тем чтобы иметь возможность
создать иерархию классов для строк базы банных, поскольку существует большая
вероятность того, что нам встретятся несколько похожих столбцов в каждой из
таблиц, входящих в схему.
Конструктор атрибута принимает два аргумента. Первый из них представляет
собой название столбца, который должен быть определен в базе данных. В качестве
второго аргумента используется значение перечислимого типа coiumnDomain, кото-
рый для данного примера состоит из четырех значений, чего было бы явно недо-
статочно для настоящей программы. . *
646 Глава 22
У атрибута также есть три других свойства, которые описание которых приво-
дится ниже:
Q Nuiiabie — По умолчанию этому свойству присваивается значение false;
данное свойство используется при создании столбца для описания того,
может ли в качестве соответствующего значения в базе данных храниться
значение NULL.
• order — Определяет порядковый номер столбца в данной таблице.
После того как таблица будет создана, столбцы будут выводиться
в возрастающем порядке. По умолчанию генерируется увеличенное
на единицу значение; это происходит внутри конструктора.
Можно переопределить это значение так, как это необходимо.
• size — Определяет максимально допустимое число символов в строке
для строкового типа.
Для определения названия столбца Name можно воспользоваться таким кодом:
[DatabaseColumn{*NAME*, . mr omain.String,Order= ( Siz 4)]
• . ; • •; :: Ш Ш
get { return (string) this ["NAME"]; }
зол { this! "IWV ! -' value; } . '• '• : • : ;'<-: ?.:. • \ • ' ' ;
AuthorRow
MuthorlD: int
+HireDate: Date
В этом примере определяется поле с именем NAME, И ОНО будет создано как
VARCHAR(64), поскольку домен данного столбца определен как строка, а параметру
size присвоено соответствующее значение. Порядковый номер в примере опреде-
ляется равным 10 — почему именно 10, мы поймем позже. В столбце также не
могут использоваться значения NULL, поскольку свойству Nuiiabie по умолчанию
присваивается значение false (т. е. данный столбец будет создан как NON NULL).
Класс DataRow представляет собой индексатор, которому в качестве параметра
передается значение поля (или порядковое числительное). В результате возвраща-
ется некоторый объект, который приводится к строковому типу, перед тем как
быть переданным получателю, приведенному выше.
Создание строк баз данных
Целью этого примера является построение семейства строго типизированных
объектов DataRow. В данном примере мы создадим два класса, Author и Book, кото-
рые являются производными от одного базового класса, поскольку они оба обла-
дают доступом к общим полям.
В классе GenericRow (см. рис. слева) определяются свойства
Name И D e s c r i p t i o n ; необходимый ДЛЯ ЭТОГО КОД НрИВОДИТСЯ ниже.
Данный класс является производным от класса DataRow — класса,
являющегося базовым классом для всех строк баз данных, исполь-
зующихся в системе.
В нашем примере два класса являются производными от клас-
са GenericRow, ОДИН ИЗ КОТОрыХ представляет автора (AuthorRow),
а второй — книгу (BookRow). Оба класса обладают дополнитель-
ными свойствами, которые привязываются к полям базы данных:
// Извлечение из файла DatabaseAttributes.cs
/// <summary>
/// Базовый класс строки — в нем описываются
/// столбцы Name и Description
II/ </suramary>
System.Data.DataRow
GenericRow
+Name: String
-•-Description: String
i
BookRow
+BooklD: int
MuthorlD: int
+ISBN: String
Атрибуты 647
public abstract class GenericRow : DataRow
:
Категория: информатика | Просмотров: 1186 | Добавил: basic | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email *:
Код *:
Календарь
«  Февраль 2010  »
ПнВтСрЧтПтСбВс
1234567
891011121314
15161718192021
22232425262728
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

krutoto.ucoz.ru
Бесплатный конструктор сайтов - uCoz