Как работают классы атрибутов? - PullRequest
53 голосов
/ 20 апреля 2010

Мои поиски продолжают показывать только руководства, объясняющие, как использовать и применять атрибуты к классу Я хочу научиться создавать свои собственные классы атрибутов и механизм их работы.

Как создаются экземпляры классов атрибутов? Они создаются, когда создается экземпляр класса, к которому они применяются? Один экземпляр для каждого экземпляра класса, к которому он применяется? Например. если я применю класс SerializableAttribute к классу MyData и создаю 5 экземпляров MyData, будут ли созданы 5 экземпляров класса SerializbleAttribute за кулисами? Или между ними есть только один экземпляр?

Как экземпляры класса атрибутов получают доступ к классу, с которым они связаны? Как класс SerializableAttribute обращается к классу, к которому он применяется, чтобы он мог сериализовать свои данные? Есть ли у него какое-то свойство SerializableAttribute.ThisIsTheInstanceIAmAppliedTo? :) Или это работает в обратном направлении, что всякий раз, когда я сериализую что-либо, функция Serialize, которой я передаю экземпляр MyClass, будет рефлексивно проходить через атрибуты и находить экземпляр SerialiableAttribute?

Ответы [ 6 ]

35 голосов
/ 20 апреля 2010

Раньше я не использовал атрибуты в своей повседневной работе, но я читал о них. Также я сделал несколько тестов, чтобы подтвердить то, что я скажу здесь. Если я ошибаюсь в любом месте - не стесняйтесь сказать мне это :) 1001 *

Из того, что я знаю, атрибуты не действуют как обычные классы. Они не создаются при создании объекта, к которому они применяются, а не по одному статическому экземпляру, а не по 1 на каждый экземпляр объекта. Они также не обращаются к классу, к которому они применяются ..

Вместо этого они действуют как свойства (атрибуты?: P) класса. Не так, как класс .NET properties , больше похоже на свойство типа «одно свойство стекла - прозрачность». Вы можете проверить, какие атрибуты применяются к классу из отражения, а затем действовать соответствующим образом. По сути, это метаданные, связанные с определением класса, а не объекты этого типа.

Вы можете попытаться получить список атрибутов для класса, метода, свойства и т. Д. И т. Д. Когда вы получите список этих атрибутов - именно здесь они будут созданы. Затем вы можете воздействовать на данные этих атрибутов.

например. в таблицах Linq свойства имеют атрибуты, определяющие, к какой таблице / столбцу они относятся. Но эти классы не используют эти атрибуты. Вместо этого DataContext проверит атрибуты этих объектов, когда преобразует деревья выражений linq в код SQL.

Теперь о некоторых реальных примерах. Я запустил их в LinqPad , так что не беспокойтесь о странном методе Dump (). Я заменил его на Console.WriteLine, чтобы облегчить понимание кода для людей, которые не знают об этом:)

void Main()
{
    Console.WriteLine("before class constructor");
    var test = new TestClass();
    Console.WriteLine("after class constructor");

    var attrs = Attribute.GetCustomAttributes(test.GetType()).Dump();
    foreach(var attr in attrs)
        if (attr is TestClassAttribute)
            Console.WriteLine(attr.ToString());
}

public class TestClassAttribute : Attribute
{
    public TestClassAttribute()
    {
        DefaultDescription = "hello";
        Console.WriteLine("I am here. I'm the attribute constructor!");
    }
    public String CustomDescription {get;set;}
    public String DefaultDescription{get;set;}

    public override String ToString()
    {
        return String.Format("Custom: {0}; Default: {1}", CustomDescription, DefaultDescription);
    }
}

[Serializable]
[TestClass(CustomDescription="custm")]
public class TestClass
{
    public int Foo {get;set;}
}

Результат консоли этого метода:

before class constructor
after class constructor
I am here. I'm the attribute constructor!
Custom: custm; Default: hello

И Attribute.GetCustomAttributes(test.GetType()) возвращает этот массив: (в таблице показаны все доступные столбцы для всех записей. Нет, атрибут Serializable не имеет этих свойств :)) LinqPad Attributes Array

Есть еще вопросы? Не стесняйтесь спрашивать!

UPD: Я видел, как вы задаете вопрос: зачем их использовать? В качестве примера я расскажу вам о библиотеке XML-RPC.NET. Вы создаете свой класс обслуживания XML-RPC с методами, которые будут представлять методы xml-rpc. Главное сейчас: в XmlRpc имена методов могут иметь некоторые специальные символы, такие как точки. Таким образом, вы можете иметь метод flexlabs.ProcessTask () xml rpc.

Вы определяете этот класс следующим образом:

[XmlRpcMethod("flexlabs.ProcessTask")]
public int ProcessTask_MyCustomName_BecauseILikeIt();

Это позволяет мне называть метод так, как мне нравится, но при этом использовать публичное имя, каким оно и должно быть.

16 голосов
/ 20 апреля 2010

Атрибуты - это, по сути, метаданные, которые можно прикрепить к различным частям вашего кода. Эти метаданные могут затем объединяться и влиять на поведение определенных операций.

Атрибуты могут применяться практически ко всем аспектам вашего кода. Например, атрибуты могут быть связаны на уровне сборки, например атрибуты AssemblyVersion и AssemblyFileVersion, которые определяют номера версий, связанных со сборкой.

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Тогда атрибут Serializable, например, можно применить к объявлению типа, чтобы пометить тип как поддерживающий сериализацию. На самом деле этот атрибут имеет особое значение в CLR и фактически хранится как специальная директива непосредственно для типа в IL, он оптимизирован для хранения в виде битового флага, который может быть обработан намного эффективнее, есть несколько атрибутов на это природа, которая известна как псевдо пользовательские атрибуты.

Еще другие атрибуты могут быть применены к методам, свойствам, полям, перечислениям, возвращаемым значениям и т. Д. Вы можете получить представление о возможных целях, к которым можно применить атрибут, просмотрев эту ссылку http://msdn.microsoft.com/en-us/library/system.attributetargets(VS.90).aspx

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

Для довольно наивного примера, и это только для примера :) Возможно, вы захотите написать механизм сохранения, который будет автоматически сопоставлять классы с таблицами в вашей базе данных и сопоставлять свойства класса со столбцами таблицы. Вы можете начать с определения двух пользовательских атрибутов

TableMappingAttribute
ColumnMappingAttribute

Который вы затем можете применить к своим классам, например, у нас есть класс Person

[TableMapping("People")]
public class Person
{
  [ColumnMapping("fname")]
  public string FirstName {get; set;}

  [ColumnMapping("lname")]
  public string LastName {get; set;}
}

Когда это компилируется, за исключением того факта, что компилятор выдает дополнительные метаданные, определенные пользовательскими атрибутами, это не затрагивает ничего другого. Однако теперь вы можете написать PersistanceManager, который может динамически проверять атрибуты экземпляра класса Person и вставлять данные в таблицу People, сопоставляя данные в свойстве FirstName со столбцом fname, а свойство LastName со столбцом lname.

Что касается вашего вопроса, касающегося экземпляров атрибутов, экземпляр атрибута создается не для каждого экземпляра вашего класса. Все экземпляры People будут совместно использовать один и тот же экземпляр TableMappingAttribute и ColumnMappingAttributes. Фактически, экземпляры атрибутов создаются только при первом запросе атрибутов.

6 голосов
/ 20 апреля 2010

Представьте себе, что атрибуты являются пост-его, которые присоединены к определениям классов или методов (встроенным в метаданные сборки).

Затем вы можете иметь модуль «процессор / бегун / инспектор», который принимает эти типы путем отражения, ищет эти пост-и обрабатывает их по-разному. Это называется декларативным программированием. Вы объявляете некоторое поведение вместо того, чтобы писать код для них в типе.

  • Атрибут Serializable для типа объявляет, что он создан для сериализации. Затем XmlSerializer может принять объект этого класса и сделать все необходимое. Вы помечаете методы, которые должны быть сериализованы / скрыты, с помощью правильной записи.
  • другим примером будет NUnit. Средство выполнения NUnit просматривает атрибуты [TestFixture] всех классов, определенных в целевой сборке, чтобы идентифицировать тестовые классы. Затем он ищет методы, помеченные атрибутом [Test], чтобы определить тесты, которые он затем выполняет, и отображает результаты.

Возможно, вы захотите просмотреть это руководство на MSDN , в котором вы найдете ответы на большинство ваших вопросов и пример в конце. Хотя они могли бы извлечь метод под названием Audit(Type anyType); вместо дублирования этого кода. Пример «печатает информацию», проверяя атрибуты ... но вы можете сделать все в том же духе.

6 голосов
/ 20 апреля 2010

Да, они создаются с параметрами, которые вы ему задаете.

Атрибут не «обращается» к классу. Атрибут прикреплен к списку атрибутов класса / свойства в данных отражения.

[Serializable]
public class MyFancyClass
{ ... }

// Somewhere Else:

public void function()
{
   Type t = typeof(MyFancyClass);
   var attributes = t.GetCustomAttributes(true);

   if (attributes.Count(p => p is SerializableAttribute) > 0)
   {
       // This class is serializable, let's do something with it!

   }     
}
2 голосов
/ 20 апреля 2010

Если вы посмотрите на этот загружаемый открытый исходный код LINQ to Active Directory (CodePlex) , вам может быть интересен механизм файла Attributes.cs, где Барт Де Смет написал все свои атрибуты определения классов. Я изучил там атрибуты.

Короче говоря, вы можете специализировать класс Attribute и кодировать некоторые специальные свойства для своих нужд.

public class MyOwnAttributeClass : Attribute {
    public MyOwnAttributeClass() {
    }
    public MyOwnAttributeClass(string myName) {
        MyName = myName;
    }
    public string MyName { get; set; }
}

и затем вы можете использовать его везде, где MyOwnAttributeClass окажется полезным. Это может быть либо определение класса, либо определение свойства.

[MyOwnAttributeClass("MyCustomerName")]
public class Customer {
    [MyOwnAttributeClass("MyCustomerNameProperty")]
    public string CustomerName { get; set; }
}

Тогда вы можете получить это через отражение следующим образом:

Attribute[] attributes = typeof(Customer).GetCustomAttribute(typeof(MyOwnAttributeClass));

Учтите, что атрибут, заключенный в квадратные скобки, всегда является конструктором вашего атрибута. Поэтому, если вы хотите иметь параметризованный атрибут, вам нужно закодировать ваш конструктор как таковой.

Этот код предоставляется как есть и может не компилироваться. Его цель - дать вам представление о том, как это работает.

Действительно, вы обычно хотите иметь другой класс атрибута для класса, чем для свойства.

Надеюсь, это поможет!

1 голос
/ 20 апреля 2010

Не так много времени, чтобы дать вам более полный ответ, но вы можете найти Атрибуты, которые были применены к значению, используя Reflection. Что касается их создания, вы наследуете от класса Attribute и работаете оттуда - и значения, которые вы предоставляете с атрибутом, передаются в конструктор класса Attribute.

Прошло много времени, как вы могли бы сказать ...

Martin

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...