Comment créer un attribut personnalisé en C #

119

J'ai essayé beaucoup de fois mais je ne suis toujours pas en mesure de comprendre l'utilisation des attributs personnalisés (j'ai déjà parcouru de nombreux liens).

Quelqu'un peut-il s'il vous plaît m'expliquer un exemple très basique d'un attribut personnalisé avec du code?

slash shogdhe
la source

Réponses:

96

Bien que le code pour créer un attribut personnalisé soit assez simple, il est très important que vous compreniez ce que sont les attributs:

Les attributs sont des métadonnées compilées dans votre programme. Les attributs eux-mêmes n'ajoutent aucune fonctionnalité à une classe, une propriété ou un module - juste des données. Cependant, en utilisant la réflexion, on peut exploiter ces attributs afin de créer des fonctionnalités.

Alors, par exemple, regardons le bloc d'application de validation , de la bibliothèque d'entreprise de Microsoft . Si vous regardez un exemple de code, vous verrez:

    /// <summary>
    /// blah blah code.
    /// </summary>
    [DataMember]
    [StringLengthValidator(8, RangeBoundaryType.Inclusive, 8, RangeBoundaryType.Inclusive, MessageTemplate = "\"{1}\" must always have \"{4}\" characters.")]
    public string Code { get; set; }

À partir de l'extrait ci-dessus, on peut deviner que le code sera toujours validé, chaque fois qu'il sera modifié, conformément aux règles du validateur (dans l'exemple, avoir au moins 8 caractères et au plus 8 caractères). Mais la vérité est que l'attribut ne fait rien; comme mentionné précédemment, il ajoute uniquement des métadonnées à la propriété.

Cependant, la bibliothèque d'entreprise a un Validation.Validate méthode qui examinera votre objet, et pour chaque propriété, elle vérifiera si le contenu enfreint la règle informée par l'attribut.

Donc, c'est ainsi que vous devriez penser aux attributs - un moyen d'ajouter des données à votre code qui pourraient être utilisées plus tard par d'autres méthodes / classes / etc.

Bruno Brant
la source
vais-je vraiment aimer la réponse et spécialement ", une autre question que je peux mettre la même condition dans la déclaration d'ensemble du code ci-dessus, alors en quoi il est différent des attributs,
slash shogdhe
1
@slash: Pouvez-vous reformuler cela? Je n'ai pas bien compris la question.
Bruno Brant le
1
Je pense que slash voulait poser des questions sur la différence entre l'utilisation d'attributs et l'insertion du code de validation réel dans le setter de propriétés. Réponse: Bien que l'écriture de code à l'intérieur du setter puisse être effectuée pour valider la valeur, l'utilisation d'attributs seuls n'effectuera pas la validation en tant que telle. Les attributs ne sont que des "méta-données". Un autre code ailleurs devrait s'intéresser aux attributs que vous utilisez, les lire et effectuer des actions basées sur eux. Un exemple typique est une bibliothèque de validation, comme @BrunoBrant l'a mentionné.
romar
10
Je ne sais pas pourquoi c'est la réponse acceptée. La vraie question (qui est également indexée dans Google) est "Comment créer un attribut personnalisé en C #". Les réponses ne plongent pas du tout dans ce sujet. La deuxième réponse fait, d'autre part.
Drakestar
Je pense que la deuxième réponse est plus liée à la question.
Mohammad Taherian
267

Vous commencez par écrire une classe qui dérive de Attribute :

public class MyCustomAttribute: Attribute
{
    public string SomeProperty { get; set; }
}

Ensuite, vous pouvez décorer n'importe quoi (classe, méthode, propriété, ...) avec cet attribut:

[MyCustomAttribute(SomeProperty = "foo bar")]
public class Foo
{

}

et enfin vous utiliseriez la réflexion pour le récupérer:

var customAttributes = (MyCustomAttribute[])typeof(Foo).GetCustomAttributes(typeof(MyCustomAttribute), true);
if (customAttributes.Length > 0)
{
    var myAttribute = customAttributes[0];
    string value = myAttribute.SomeProperty;
    // TODO: Do something with the value
}

Vous pouvez limiter les types de cibles auxquels cet attribut personnalisé peut être appliqué à l'aide de l' attribut AttributeUsage :

/// <summary>
/// This attribute can only be applied to classes
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class MyCustomAttribute : Attribute

Choses importantes à savoir sur les attributs:

  • Les attributs sont des métadonnées.
  • Ils sont intégrés à l'assemblage au moment de la compilation, ce qui a des implications très sérieuses sur la façon dont vous pouvez définir leurs propriétés. Seules les valeurs constantes (connues au moment de la compilation) sont acceptées
  • La seule façon de donner un sens et d'utiliser des attributs personnalisés est d'utiliser Reflection . Donc, si vous n'utilisez pas la réflexion au moment de l'exécution pour les récupérer et décorer quelque chose avec un attribut personnalisé, ne vous attendez pas à grand chose.
  • Le moment de la création des attributs est non déterministe. Ils sont instanciés par le CLR et vous n'en avez absolument aucun contrôle.
Darin Dimitrov
la source
3
Où, dans quelle fonction / classe, dois-je utiliser la réflexion pour le récupérer?
Hasan A Yousef
@Hasan A Yousef, par exemple dans Entity Framework, il y a un attribut "Key" qui indique au framework: Cette propriété doit être considérée comme une clé primaire. Lors de la création d'ORM, les attributs sont très utiles
Parsa
Comment accéder à un attribut personnalisé sur une propriété et non sur une classe?
Toile
docs.microsoft.com/en-us/dotnet/standard/attributes/ ... juste pour être complet, cette page msdn le résume très bien
Barış Akkurt
Avec les génériques, il est beaucoup, beaucoup plus facile d'obtenir les types:var value = typeof(Foo).GetCustomAttributes<MyCustomAttribute>().First().SomeProperty;
jpaugh
27

En utilisant / Copiant la grande réponse de Darin Dimitrov, voici comment accéder à un attribut personnalisé sur une propriété et non sur une classe:

La propriété décorée [de classe Foo]:

[MyCustomAttribute(SomeProperty = "This is a custom property")]
public string MyProperty { get; set; }

Récupérer:

PropertyInfo propertyInfo = typeof(Foo).GetProperty(propertyToCheck);
object[] attribute = propertyInfo.GetCustomAttributes(typeof(MyCustomAttribute), true);
if (attribute.Length > 0)
{
    MyCustomAttribute myAttribute = (MyCustomAttribute)attribute[0];
    string propertyValue = myAttribute.SomeProperty;
}

Vous pouvez lancer ceci dans une boucle et utiliser la réflexion pour accéder à cet attribut personnalisé sur chaque propriété de la classe Foo, ainsi:

foreach (PropertyInfo propertyInfo in Foo.GetType().GetProperties())
{
    string propertyName = propertyInfo.Name;

    object[] attribute = propertyInfo.GetCustomAttributes(typeof(MyCustomAttribute), true);
    // Just in case you have a property without this annotation
    if (attribute.Length > 0)
    {
        MyCustomAttribute myAttribute = (MyCustomAttribute)attribute[0];
        string propertyValue = myAttribute.SomeProperty;
        // TODO: whatever you need with this propertyValue
    }
}

Grand merci à toi, Darin !!

Hopper
la source
comment étendre cela si nous ne savons pas quels types d'attributs existent sur une propriété? object[] attribute = propertyInfo.GetCustomAttributes(typeof(???), true);Je veux juste les parcourir tous et appeler une méthode m1()de chaque attribut inconnu
heyNow
0

La réponse courte est pour créer un attribut en c #, il vous suffit de l'hériter de la classe Attribute, juste ceci :)

Mais ici, je vais expliquer les attributs en détail:

fondamentalement, les attributs sont des classes que nous pouvons les utiliser pour appliquer notre logique aux assemblys, classes, méthodes, propriétés, champs, ...

Dans .Net, Microsoft a fourni des attributs prédéfinis tels que des attributs obsolètes ou de validation tels que ([Obligatoire], [StringLength (100)], [Range (0, 999.99)]), ainsi que des types d'attributs comme ActionFilters dans asp.net qui peut être très utile pour appliquer notre logique souhaitée à nos codes (lisez cet article sur les filtres d'action si vous êtes passionné de l'apprendre)

un autre point, vous pouvez appliquer une sorte de configuration sur votre attribut via AttibuteUsage.

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]

Lorsque vous décorez une classe d'attribut avec AttributeUsage, vous pouvez dire au compilateur c # où je vais utiliser cet attribut: je vais l'utiliser sur des classes, sur des assemblys sur des propriétés ou sur ... et mon attribut est autorisé à utiliser plusieurs fois sur des cibles définies (classes, assemblages, propriétés, ...) ou pas?!

Après cette définition des attributs, je vais vous montrer un exemple: imaginez que nous voulons définir une nouvelle leçon à l'université et que nous voulons permettre uniquement aux administrateurs et aux maîtres de notre université de définir une nouvelle leçon, d'accord?

namespace ConsoleApp1
{
    /// <summary>
    /// All Roles in our scenario
    /// </summary>
    public enum UniversityRoles
    {
        Admin,
        Master,
        Employee,
        Student
    }

    /// <summary>
    /// This attribute will check the Max Length of Properties/fields
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
    public class ValidRoleForAccess : Attribute
    {
        public ValidRoleForAccess(UniversityRoles role)
        {
            Role = role;
        }
        public UniversityRoles Role { get; private set; }

    }


    /// <summary>
    /// we suppose that just admins and masters can define new Lesson
    /// </summary>
    [ValidRoleForAccess(UniversityRoles.Admin)]
    [ValidRoleForAccess(UniversityRoles.Master)]
    public class Lesson
    {
        public Lesson(int id, string name, DateTime startTime, User owner)
        {
            var lessType = typeof(Lesson);
            var validRolesForAccesses = lessType.GetCustomAttributes<ValidRoleForAccess>();

            if (validRolesForAccesses.All(x => x.Role.ToString() != owner.GetType().Name))
            {
                throw new Exception("You are not Allowed to define a new lesson");
            }
            
            Id = id;
            Name = name;
            StartTime = startTime;
            Owner = owner;
        }
        public int Id { get; private set; }
        public string Name { get; private set; }
        public DateTime StartTime { get; private set; }

        /// <summary>
        /// Owner is some one who define the lesson in university website
        /// </summary>
        public User Owner { get; private set; }

    }

    public abstract class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
    }


    public class Master : User
    {
        public DateTime HireDate { get; set; }
        public Decimal Salary { get; set; }
        public string Department { get; set; }
    }

    public class Student : User
    {
        public float GPA { get; set; }
    }



    class Program
    {
        static void Main(string[] args)
        {

            #region  exampl1

            var master = new Master()
            {
                Name = "Hamid Hasani",
                Id = 1,
                DateOfBirth = new DateTime(1994, 8, 15),
                Department = "Computer Engineering",
                HireDate = new DateTime(2018, 1, 1),
                Salary = 10000
            };
            var math = new Lesson(1, "Math", DateTime.Today, master);

            #endregion

            #region exampl2
            var student = new Student()
            {
                Name = "Hamid Hasani",
                Id = 1,
                DateOfBirth = new DateTime(1994, 8, 15),
                GPA = 16
            };
            var literature = new Lesson(2, "literature", DateTime.Now.AddDays(7), student);
            #endregion

            ReadLine();
        }
    }


}

Dans le monde réel de la programmation, nous n'utilisons peut-être pas cette approche pour utiliser les attributs et je l'ai dit en raison de son intérêt pédagogique dans l'utilisation des attributs

Hamid
la source