Skip to content

Instantly share code, notes, and snippets.

@eterekhin
Last active January 1, 2020 06:01
Show Gist options
  • Save eterekhin/ca0de518098c60d436c5d9e90c7e2535 to your computer and use it in GitHub Desktop.
Save eterekhin/ca0de518098c60d436c5d9e90c7e2535 to your computer and use it in GitHub Desktop.

Все атрибуты в .net должны наследоваться от класса System.Attribute. При этом создавая атрибут можно задать ему несколько свойств(используя атрибут AttributeUsageAttribute(у него есть такой же атрибут, кстати))

По умолчанию любой атрибут можно вешать на что угодно и использовать сколько угодно раз, и у наследников он тоже будет использоваться
Я думаю, что самая идея атрибутов которые можно вешать а потом рефлексивно получать значение очевдина,интереснее как это устроено внутри
Атрибуты можно вешать на классы, методы, аргументы методов,возвращаемое методом значение, свойства, поля, события, геттеры, сеттеры, конструктор, структуры..
Практически куда угодно
Можно повесить атрибут на generic

  public class TestGenericParameterAttribute<[Test] T>
    {
    }
  // а потом достать его
      var attributes = typeof(TestGenericParameterAttribute<>)
                .GetGenericArguments()[0]
                .CustomAttributes
                .ToArray();

Атрибуты делятся на genuine custom (подлинно пользовательские) и pseudo-custom (псевдо пользовательские), первые можно определять самостоятельно и забирать используя рефлексию, параметры атрибута хранятся в метаданных сборки и атрибут создается при обращении к нему, pseudo-custom attributes не хранятся в метаданных, они применяются при компиляции изменяя таблицы метаданных типов, после этого все данные об этих атрибутах сбрасываются и из уже нельзя достать используя рефлексию

Пример pseudo-custom attributes: FieldOffsetAttribute - позволяет явно задавать смещения полей в классах и структурах InAttribute - показывает, что этот параметр в методе передается как In параметр(проставляет флаг Param.Flag.In для параметра метода),

//     in - переданные в метод параметр не должен меняться
        public void Test(in string str)
        {
        }
        

OutAttribute - аналогично In атрибуту, только для out параметров

Пример genuine custom attribute - любой атрибут, который может создать программист, из добавленных в BCL Flags - используется только для того, чтобы ToString выводил значения enum'a через запятую ApiController - asp.net

использование genuim custom атрибутов выгоднее нежели pseudo-genuim custom, потому что они на этапе компиляции проставляют флаги в метаданных, при использовании pseudo-genuim custom атрибутов приходится задействовать рефлексию, есть два основных метода для работы с ними:

  1. (определен в RunTimeType) bool IsDefined(Type attributeType,bool inherited) - проверяет наличие атрибута, довольно производительный, поскольку просто смотрит, есть ли параметры для этого attribut'a в метаданных
  2. интерфейс ICustomAttributeProvider

Реализуется большинством reflection типов

MemberInfo в свою очередь наследует FieldInfo, PropertyInfo, EventInfo, MethodBase и TypeInfo.
Параметр inherit определяет, стоит ли искать этот атрибут у классов-предков
Эти же методы доступны как статические в классе Attribute, аргумент inherit там опущен, и по умолчанию сделан поиск в наследниках
Но самый удобный метод это extension в классе IEnumerable CustomAttributeExtensions(this Type target,Type attributeType) - поиск в наследниках по умолчанию(есть перегрузка метода принимающая inherit)
Также существует аналогичный метод IEnumerable CustomAttributeExtension(this Type target,Type attributeType). Эти методы просто оборачивают вызовы методов класса Attribute, лучше всегда использовать их, версию возвращающую коллекцию имеет смысл использовать только если у атрибута в AttributeUsage указан параметр AllowMultiple = true.

DebuggerTypeProxy

Также хочется рассмотреть интересный атрибут - DebuggerTypeProxy, этот атрибут позволяет изменить отображение объекта в отладчике, например Human отображается, как HumanDisplayForDebugger которому в конструктор передан экземпляр human

DebuggerDisplay

Когда нет возможности переопределить ToString

DebuggerBrowsableAttribute

Позволяет запретить показывать элемент в debugger'e:

Либо, наоборот, развернуть(для коллекций, все остальные элементы класса показаны не будут):

Что происходит при обращении к атрибутам

Если методом рефлексии на выходе возвращается атрибут, значит происходит создание атрибута из параметров, которые ему передали Эти параметры хранятся в метаданных сборки, и при обращении к атрибуту (например, методом GetCustomAttribute), подставляются в конструктор атрибута, потом заполняются именованные поля, и возвращается объект. Т.Е существуют определенные накладные расходы на все это

Если нужно просто проверить наличие атрибута, можно воспользоваться методом bool Attribute.IsDefined(Type target,Type attribute) тогда не произойдет создание - будет проверено только наличие без каких-либо накладных расходов

Существует статический метод CustomAttributeData.GetCustomAttributes - который возвращает информацию об атрибуте без его создания:

![](https://habrastorage.org/webt/yx/oi/sa/yxoisaw_eumkok4u8vgtla5ckk0.

Создание атрибута также опасно с точки зрения безопасности, поскольку в сборке может храниться любой код, потенциально ломающий программу

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment