web-dev-qa-db-fra.com

Comment fonctionnent les classes d'attributs?

Mes recherches ne cessent de produire que des guides expliquant comment utiliser et appliquer des attributs à une classe. Je veux apprendre à créer mes propres classes d'attributs et la mécanique de leur fonctionnement.

Comment les classes d'attributs sont-elles instanciées? Sont-elles instanciées lorsque la classe à laquelle elles sont appliquées est instanciée? Une instance est-elle instanciée pour chaque classe à laquelle elle est appliquée? Par exemple. si j'applique la classe SerializableAttribute à une classe MyData et que j'instancie 5 instances MyData, y aura-t-il 5 instances de la classe SerializbleAttribute créées en arrière-plan? Ou y a-t-il une seule instance partagée entre tous?

Comment les instances de classe d'attributs accèdent-elles à la classe à laquelle elles sont associées? Comment une classe SerializableAttribute accède-t-elle à la classe à laquelle elle est appliquée afin de pouvoir sérialiser ses données? A-t-il une sorte de propriété SerializableAttribute.ThisIsTheInstanceIAmAppliedTo? :) Ou cela fonctionne-t-il dans le sens inverse que chaque fois que je sérialise quelque chose, la fonction Serialize à laquelle je passe l'instance MyClass passe en revue de manière réfléchie les attributs et trouve l'instance SerialiableAttribute?

56
AaronLS

Je n'ai pas utilisé d'attributs dans mon travail quotidien auparavant, mais j'ai lu à leur sujet. J'ai aussi fait quelques tests, pour sauvegarder ce que je vais dire ici. Si je me trompe, n'hésitez pas à me le dire :)

D'après ce que je sais, les attributs n'agissent pas comme des classes régulières. Ils ne sont pas instanciés lorsque vous créez un objet auquel ils sont appliqués, pas une instance statique, pas 1 pour chaque instance de l'objet. Ils n'accèdent pas non plus à la classe à laquelle ils sont appliqués.

Au lieu de cela, ils agissent comme des propriétés (attributs?: P) de la classe. Pas comme la classe .NET propriétés, plus comme dans la propriété "une propriété du verre est la transparence". Vous pouvez vérifier quels attributs sont appliqués à une classe à partir de la réflexion, puis agir en conséquence. Ce sont essentiellement des métadonnées attachées à la définition de classe, et non les objets de ce type.

Vous pouvez essayer d'obtenir la liste des attributs sur une classe, une méthode, une propriété, etc. Lorsque vous obtenez la liste de ces attributs - c'est là qu'ils seront instanciés. Ensuite, vous pouvez agir sur les données de ces attributs.

Par exemple. les tables Linq, les propriétés ont des attributs qui définissent à quelle table/colonne ils se réfèrent. Mais ces classes n'utilisent pas ces attributs. Au lieu de cela, le DataContext vérifiera les attributs de ces objets lorsqu'il convertira les arborescences d'expression linq en code SQL.

Maintenant, pour de vrais exemples .. Je les ai exécutés dans LinqPad , alors ne vous inquiétez pas de l'étrange méthode Dump (). Je l'ai remplacé par Console.WriteLine pour rendre le code plus facile à comprendre pour les gens qui ne le connaissent pas :)

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;}
}

Le résultat console de cette méthode est:

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

Et la Attribute.GetCustomAttributes(test.GetType()) renvoie ce tableau: (le tableau montre toutes les colonnes disponibles pour toutes les entrées .. Donc non, l'attribut Serializable n'a pas ces propriétés :)) LinqPad Attributes Array

Avez vous d'autres questions? N'hésitez pas à demander!

UPD: Je vous ai vu poser une question: pourquoi les utiliser? À titre d'exemple, je vais vous parler de la bibliothèque XML-RPC.NET. Vous créez votre classe de service XML-RPC, avec des méthodes qui représenteront les méthodes xml-rpc. La chose principale en ce moment est: dans XmlRpc, les noms de méthode peuvent avoir des caractères spéciaux, comme des points. Ainsi, vous pouvez avoir une méthode rpc flexlabs.ProcessTask () xml.

Vous définiriez cette classe comme suit:

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

Cela me permet de nommer la méthode comme je l'aime, tout en utilisant le nom public tel qu'il doit être.

35
Artiom Chilaru

Les attributs sont essentiellement des métadonnées qui peuvent être attachées à divers éléments de votre code. Ces métadonnées peuvent alors être inter-portes et affecter le comportement de certaines opérations.

Les attributs peuvent être appliqués à presque tous les aspects de votre code. Par exemple, les attributs peuvent être associés au niveau de l'assembly, comme les attributs AssemblyVersion et AssemblyFileVersion, qui régissent les numéros de version associés à l'assembly.

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

Ensuite, l'attribut Serializable par exemple peut être appliqué à une déclaration de type pour marquer le type comme prenant en charge la sérialisation. En fait, cet attribut a une signification particulière au sein du CLR et est en fait stocké en tant que directive spéciale directement sur le type dans l'IL, il est optimisé pour être stocké comme un indicateur de bit qui peut être traité beaucoup plus efficacement, il y a quelques attributs sur cette nature, connue sous le nom d'attributs pseudo personnalisés.

D'autres attributs peuvent encore être appliqués aux méthodes, propriétés, champs, énumérations, valeurs de retour, etc. Vous pouvez vous faire une idée des cibles possibles auxquelles un attribut peut être appliqué en consultant ce lien http://msdn.Microsoft .com/en-us/library/system.attributetargets (VS.90) .aspx

De plus, vous pouvez définir vos propres attributs personnalisés qui peuvent ensuite être appliqués aux cibles applicables auxquelles vos attributs sont destinés. Ensuite, au moment de l'exécution, votre code pourrait réfléchir aux valeurs contenues dans les attributs personnalisés et prendre les mesures appropriées.

Pour un exemple plutôt naïf, et c'est juste pour le plaisir de l'exemple :) Vous voudrez peut-être écrire un moteur de persistance qui mappera automatiquement les classes aux tables de votre base de données et mappera les propriétés des colonnes classe aux tables. Vous pouvez commencer par définir deux attributs personnalisés

TableMappingAttribute
ColumnMappingAttribute

Que vous pouvez ensuite appliquer à vos classes, par exemple, nous avons une classe Personne

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

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

Lorsque cela se compile, à part le fait que le compilateur émet les métadonnées supplémentaires définies par les attributs personnalisés, peu d'autre est affecté. Cependant, vous pouvez maintenant écrire un PersistanceManager qui peut inspecter dynamiquement les attributs d'une instance de la classe Person et insérer les données dans la table People, en mappant les données de la propriété FirstName à la colonne fname et la propriété LastName à la colonne lname.

Quant à votre question concernant les instances des attributs, l'instance de l'attribut n'est pas créée pour chaque instance de votre classe. Toutes les instances de People partageront la même instance de TableMappingAttribute et ColumnMappingAttributes. En fait, les instances d'attribut ne sont créées que lorsque vous interrogez réellement les attributs la première fois.

16
Chris Taylor

Pensez aux attributs sont des post-its attachés aux définitions de classes ou de méthodes (intégrées dans les métadonnées Assembly).

Vous pouvez alors avoir un module processeur/runner/inspecteur qui accepte ces types en réfléchissant, recherche ces post-its et les gère différemment. C'est ce qu'on appelle la programmation déclarative. Vous déclarez un comportement au lieu d'écrire du code pour eux dans le type.

  • L'attribut sérialisable sur un type déclare qu'il est construit pour être sérialisé. Le XmlSerializer peut alors accepter un objet de cette classe et faire le nécessaire. Vous marquez les méthodes qui doivent être sérialisées/masquées avec les bons post-its.
  • un autre exemple serait le NUnit. Le runner NUnit examine les attributs [TestFixture] toutes les classes définies dans l'assembly cible pour identifier les classes de test. Il recherche ensuite les méthodes marquées de l'attribut [Test] pour identifier les tests, qu'il exécute ensuite et affiche les résultats.

Vous voudrez peut-être parcourir ce tutoriel sur MSDN qui a répondu à la plupart de vos questions avec un exemple à la fin. Bien qu'ils auraient pu extraire une méthode appelée Audit(Type anyType); au lieu de dupliquer ce code. L'exemple "imprime des informations" en inspectant les attributs .. mais vous pouvez faire n'importe quoi dans la même veine.

6
Gishu

Oui, ils sont instanciés avec les paramètres que vous lui donnez.

L'attribut n'accède pas à la classe. L'attribut est attaché à la liste d'attributs de la classe/propriété dans les données de réflexion.

[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!

   }     
}
6
Aren

Si vous jetez un œil à ce code open source téléchargeable LINQ to Active Directory (CodePlex) , vous pourriez trouver intéressant le mécanisme du fichier Attributes.cs où Bart De Smet a écrit toutes ses définitions de classes d'attributs . J'y ai appris des attributs.

En bref, vous pouvez spécialiser la classe Attribute et coder certaines propriétés spécialisées selon vos besoins.

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

puis, vous pouvez l'utiliser partout où MyOwnAttributeClass devient utile. Il peut s'agir d'une définition de classe ou d'une définition de propriété.

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

Ensuite, vous pouvez l'obtenir à travers la réflexion comme ceci:

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

Considérez que l'attribut que vous mettez entre crochets est toujours le constructeur de votre attribut. Donc, si vous voulez avoir un attribut paramétré, vous devez coder votre constructeur comme tel.

Ce code est fourni tel quel et peut ne pas être compilé. Son but est de vous donner une idée de son fonctionnement.

En effet, vous voulez généralement avoir une classe d'attributs différente pour une classe que pour une propriété.

J'espère que cela t'aides!

2
Will Marcouiller

Pas beaucoup de temps pour vous donner une réponse plus complète, mais vous pouvez trouver les attributs qui ont été appliqués à une valeur à l'aide de la réflexion. Quant à leur création, vous héritez de la classe d'attributs et travaillez à partir de là - et les valeurs que vous fournissez avec un attribut sont transmises au constructeur de la classe d'attributs.

Cela fait un moment, comme vous pourrez le dire ...

Martin

1
Martin Milan