web-dev-qa-db-fra.com

Comment obtenir le membre auquel mon attribut personnalisé a été appliqué?

Je crée un attribut personnalisé en C # et je veux faire différentes choses selon que l'attribut est appliqué à une méthode par rapport à une propriété. Au début, j'allais faire new StackTrace().GetFrame(1).GetMethod() dans mon constructeur d'attribut personnalisé pour voir quelle méthode appelait le constructeur d'attribut, mais maintenant je ne sais pas ce que cela me donnera. Que faire si l'attribut a été appliqué à une propriété? GetMethod() retournerait-elle une instance de MethodBase pour cette propriété? Existe-t-il une manière différente d'obtenir le membre auquel un attribut a été appliqué en C #?

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property,
    AllowMultiple = true)]
public class MyCustomAttribute : Attribute

Mise à jour: d'accord, j'ai peut-être posé la mauvaise question. À partir d'une classe d'attributs personnalisés, comment puis-je obtenir le membre (ou la classe contenant le membre) auquel mon attribut personnalisé a été appliqué? Aaronaught a suggéré de ne pas remonter la pile pour trouver le membre de la classe auquel mon attribut a été appliqué, mais comment pourrais-je obtenir ces informations dans le constructeur de mon attribut?

55
Sarah Vessels

Puisqu'il semble y avoir beaucoup de confusion quant au fonctionnement des cadres de pile et des méthodes, voici une démonstration simple:

static void Main(string[] args)
{
    MyClass c = new MyClass();
    c.Name = "MyTest";
    Console.ReadLine();
}

class MyClass
{
    private string name;

    void TestMethod()
    {
        StackTrace st = new StackTrace();
        StackFrame currentFrame = st.GetFrame(1);
        MethodBase method = currentFrame.GetMethod();
        Console.WriteLine(method.Name);
    }

    public string Name
    {
        get { return name; }
        set
        {
            TestMethod();
            name = value;
        }
    }
}

Le résultat de ce programme sera:

set_Name

Les propriétés en C # sont une forme de sucre syntaxique. Ils se compilent en méthodes getter et setter dans l'IL, et il est possible que certains langages .NET ne les reconnaissent même pas comme des propriétés - la résolution des propriétés se fait entièrement par convention, il n'y a pas vraiment de règles dans la spécification IL.

Maintenant, disons pour le moment que vous aviez une très bonne raison pour qu'un programme veuille examiner sa propre pile (et il y a quelques précieux raisons pratiques pour le faire). Pourquoi diable voudriez-vous qu'il se comporte différemment pour les propriétés et les méthodes?

La raison d'être des attributs est qu'ils sont une sorte de métadonnées. Si vous voulez un comportement différent, codez-le dans l'attribut. Si un attribut peut signifier deux choses différentes selon qu'il est appliqué à une méthode ou à une propriété - alors vous devriez avoir deux attributs. Définissez la cible du premier sur AttributeTargets.Method et le second à AttributeTargets.Property. Facile.

Mais encore une fois, parcourir votre propre pile pour récupérer certains attributs de la méthode d'appel est au mieux dangereux. D'une certaine manière, vous gelez la conception de votre programme, ce qui rend beaucoup plus difficile l'extension ou la refonte pour quiconque. Ce n'est pas ainsi que les attributs sont normalement utilisés. Un exemple plus approprié, serait quelque chose comme un attribut de validation:

public class Customer
{
    [Required]
    public string Name { get; set; }
}

Ensuite, votre code de validation, qui ne sait rien de l'entité réelle transmise, peut le faire:

public void Validate(object o)
{
    Type t = o.GetType();
    foreach (var prop in
        t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
    {
        if (Attribute.IsDefined(prop, typeof(RequiredAttribute)))
        {
            object value = prop.GetValue(o, null);
            if (value == null)
                throw new RequiredFieldException(prop.Name);
        }
    }
}

En d'autres termes, vous examinez les attributs d'un instance qui vous a été donnée mais dont vous ne connaissez pas nécessairement le type. Attributs XML, attributs de contrat de données, même attributs d'attribut - presque tous les attributs du .NET Framework sont utilisés de cette façon, pour implémenter des fonctionnalités dynamiques par rapport au type d'instance mais pas par respect au état du programme ou ce qui se trouve sur la pile. Il est très peu probable que vous en ayez le contrôle au moment où vous créez la trace de pile.

Je vais donc recommander à nouveau que vous ne pas utilisez l'approche de la marche à pile, sauf si vous avez une très bonne raison de le faire dont vous ne nous avez pas encore parlé. Sinon, vous risquez de vous retrouver dans un monde de souffrance.

Si vous devez absolument (ne dites pas que nous ne vous avons pas averti), utilisez deux attributs, l'un qui peut s'appliquer aux méthodes et l'autre qui peut s'appliquer aux propriétés. Je pense que vous constaterez qu'il est beaucoup plus facile de travailler avec qu'un seul super-attribut.

41
Aaronaught

Les attributs fournissent des métadonnées et ne savent rien de la chose (classe, membre, etc.) qu'ils décorent. En revanche, l'objet décoré peut demander les attributs avec lesquels il est décoré.

Si vous devez connaître le type de l'objet décoré, vous devrez le transmettre explicitement à votre attribut dans son constructeur.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, 
    AllowMultiple = true)] 
public class MyCustomAttribute : Attribute
{
   Type type;

   public MyCustomAttribute(Type type)
   {
      this.type = type;
   }
}
40
Scott Dorman

GetMethod vous renverra toujours le nom de la fonction. S'il s'agit d'une propriété, vous obtiendrez soit get_PropertyName ou set_PropertyName.

Une propriété est essentiellement un type de méthode, donc lorsque vous implémentez une propriété, le compilateur crée deux fonctions distinctes dans les méthodes MSIL résultantes, une get_ et une set_ méthodes. C'est pourquoi dans la trace de pile vous recevez ces noms.

4
Amirshk

les attributs personnalisés sont activés par du code appelant la méthode GetCustomAttributes sur ICustomAttributeProvider (objet de réflexion) qui représente l'emplacement où l'attribut est appliqué. Ainsi, dans le cas d'une propriété, un certain code obtiendrait le PropertyInfo pour la propriété, puis appelait GetCustomAttributes à ce sujet.

Si vous souhaitez créer un cadre de validation, vous devez écrire le code qui inspecte les types et les membres pour les attributs personnalisés. Vous pouvez par exemple avoir une interface qui implémente des attributs pour participer à votre framework de validation. Pourrait être aussi simple que ce qui suit:

public interface ICustomValidationAttribute
{
    void Attach(ICustomAttributeProvider foundOn);
}

Votre code pourrait rechercher cette interface sur (par exemple) un Type:

var validators = type.GetCustomAttributes(typeof(ICustomValidationAttribute), true);
foreach (ICustomValidationAttribute validator in validators)
{
     validator.Attach(type);
}

(on peut supposer que vous parcourez le graphique de réflexion dans son intégralité et que vous le faites pour chaque ICustomAttributeProvider). Pour un exemple d'une approche similaire en action dans le .net FX, vous pouvez regarder les "comportements" de WCF (IServiceBehavior, IOperationBehavior, etc.).

Mise à jour: le .net FX a une sorte de cadre général d'interception, mais essentiellement non documenté sous la forme de ContextBoundObject et ContextAttribute. Vous pouvez rechercher sur le Web quelques exemples de son utilisation pour AOP.

4
alexdej