Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
// Value : 01 00 28 41 20 76 65 72 79 20 6c 6f 6e 67 2c 20 > (A very long, <
// : 73 6c 6f 77 2c 20 62 75 74 20 66 65 61 74 75 72 >slow, but feature<
// : 65 2d 72 69 63 68 20 61 75 74 6f 00 00 >e-rich auto <
// ctor args: ("A very long, slow, but feature-rich auto")
Ограничение использования атрибутов
По умолчанию специальные атрибуты могут быть применены практически к любому аспекту кода (методам, классам, свойствам и т.д.). Таким образом, если бы это имело смысл, то VehicleDescription можно было бы использовать для уточнения методов, свойств или полей (помимо прочего):
[VehicleDescription("A very long, slow, but feature-rich auto")]
public class Winnebago
{
[VehicleDescription("My rocking CD player")]
public void PlayMusic(bool On)
{
...
}
}
В одних случаях такое поведение является точно таким, какое требуется, но в других может возникнуть желание создать специальный атрибут, применяемый только к избранным элементам кода. Чтобы ограничить область действия специального атрибута, понадобится добавить к его определению атрибут [AttributeUsage], который позволяет предоставлять любую комбинацию значений (посредством операции "ИЛИ") из перечисления AttributeTargets:
// Это перечисление определяет возможные целевые элементы для атрибута.
public enum AttributeTargets
{
All, Assembly, Class, Constructor,
Delegate, Enum, Event, Field, GenericParameter,
Interface, Method, Module, Parameter,
Property, ReturnValue, Struct
}
Кроме того, атрибут [AttributeUsage] допускает необязательную установку именованного свойства(AllowMultiple), которое указывает, может ли атрибут применяться к тому же самому элементу более одного раза (стандартным значением является false). Вдобавок [AttributeUsage] разрешает указывать, должен ли атрибут наследоваться производными классами, с использованием именованного свойства Inherited (со стандартным значением true).
Модифицируйте определение VehicleDescriptionAttribute для указания на то, что атрибут [VehicleDescription] может применяться только к классу или структуре:
// На этот раз для аннотирования специального атрибута
// используется атрибут AttributeUsage.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
public sealed class VehicleDescriptionAttribute : System.Attribute
{
...
}
Теперь если разработчик попытается применить атрибут [VehicleDescription] не к классу или структуре, то компилятор сообщит об ошибке.
Атрибуты уровня сборки
Атрибуты можно также применять ко всем типам внутри отдельной сборки, используя дескриптор [assembly:]. Например, предположим, что необходимо обеспечить совместимость с CLS для всех открытых членов во всех открытых типах, определенных внутри сборки. Рекомендуется добавить в проект новый файл по имени AssemblyAttributes.cs (не AssemblyInfо.cs, т.к. он генерируется автоматически) и поместить в него атрибуты уровня сборки.
На заметку! Какая-либо формальная причина для использования отдельного файла отсутствует; это связано чисто с удобством поддержки вашего кода. Помещение атрибутов сборки в отдельный файл проясняет тот факт, что в вашем проекте используются атрибуты уровня сборки, и показывает, где они находятся.
При добавлении в проект атрибутов уровня сборки или модуля имеет смысл придерживаться следующей рекомендуемой схемы для файла кода:
// Первыми перечислить операторы using.
using System;
// Теперь перечислить атрибуты уровня сборки или модуля.
// Обеспечить совместимость с CLS для всех открытых типов в данной сборке.
[assembly: CLSCompliant(true)]
Если теперь добавить фрагмент кода, выходящий за рамки спецификации CLS (вроде открытого элемента данных без знака), тогда компилятор выдаст предупреждение:
// Тип ulong не соответствует спецификации CLS.
public class Winnebago
{
public ulong notCompliant;
}
На заметку! В .NET Core внесены два значительных изменения. Первое касается того, что файл AssemblyInfo.cs теперь генерируется автоматически из свойств проекта и настраивать его не рекомендуется. Второе (и связанное) изменение заключается в том, что многие из предшествующих атрибутов уровня сборки (Version, Company и т.д.) были заменены свойствами в файле проекта.
Использование файла проекта для атрибутов сборки
Как было демонстрировалось в главе 16 с классом InternalsVisibleToAttribute, атрибуты сборки можно также добавлять в файл проекта. Загвоздка здесь в том, что применять подобным образом можно только однострочные атрибуты параметров. Это справедливо для свойств, которые могут устанавливаться на вкладке Package (Пакет) в окне свойств проекта.
На заметку! На момент написания главы в хранилище GitHub для MSBuild шло активное обсуждение относительно добавления возможности поддержки нестроковых параметров, что позволило бы добавлять атрибут CLSCompliant с использованием файла проекта вместо файла *.cs.
Установите несколько свойств (таких как Authors, Description), щелкнув правой кнопкой мыши на имени проекта в окне Solution Explorer, выберите в контекстном меню пункт Properties (Свойства) и в открывшемся окне свойств перейдите на вкладку Package. Кроме того, добавьте InternalsVisibleToAttribute, как делалось в главе 16. Содержимое вашего файла проекта должно выглядеть примерно так, как представленное ниже:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Authors>Philip Japikse</Authors>
<Company>Apress</Company>
<Description>This is a simple car library with attributes</Description>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.
InternalsVisibleToAttribute">
<_Parameter1>CSharpCarClient</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
После компиляции своего проекта перейдите в каталог objDebugnet5.0 и отыщите файл AttributedCarLibrary.AssemblyInfo.cs. Открыв его, вы увидите установленные свойства в виде атрибутов (к сожалению, они не особо читабельны в таком формате):
using System;
using System.Reflection;
[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute
("CSharpCarClient")]
[assembly: System.Reflection.AssemblyCompanyAttribute("Philip Japikse")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyDescriptionAttribute("This is a
sample car library with attributes")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("AttributedCarLibrary")]
[assembly: System.Reflection.AssemblyTitleAttribute("AttributedCarLibrary")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
И последнее замечание, касающееся атрибутов сборки: вы можете отключить генерацию файла AssemblyInfо.cs, если хотите управлять процессом самостоятельно.
Рефлексия атрибутов с использованием раннего связывания
Вспомните, что атрибуты остаются бесполезными до тех пор, пока к их значениям не будет применена рефлексия в другой части программного обеспечения. После обнаружения атрибута другая часть кода может предпринять необходимый образ действий. Подобно любому приложению "другая часть программного обеспечения" может обнаруживать присутствие специального атрибута с