Cómo crear un atributo personalizado en C #

119

Lo he intentado muchas veces, pero todavía no puedo entender el uso de atributos personalizados (ya he revisado muchos enlaces).

¿Alguien puede explicarme un ejemplo muy básico de un atributo personalizado con código?

barra shogdhe
fuente

Respuestas:

96

Si bien el código para crear un atributo personalizado es bastante simple, es muy importante que comprenda qué atributos son:

Los atributos son metadatos compilados en su programa. Los atributos en sí mismos no agregan ninguna funcionalidad a una clase, propiedad o módulo, solo datos. Sin embargo, usando la reflexión, uno puede aprovechar esos atributos para crear funcionalidad.

Entonces, por ejemplo, veamos el Bloque de aplicación de validación , de la Biblioteca empresarial de Microsoft . Si observa un ejemplo de código, verá:

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

A partir del fragmento anterior, se podría suponer que el código siempre se validará, siempre que se cambie, de acuerdo con las reglas del Validador (en el ejemplo, tenga al menos 8 caracteres y como máximo 8 caracteres). Pero la verdad es que el atributo no hace nada; como se mencionó anteriormente, solo agrega metadatos a la propiedad.

Sin embargo, Enterprise Library tiene un Validation.Validatemétodo que examinará su objeto y, para cada propiedad, verificará si el contenido viola la regla informada por el atributo.

Entonces, así es como debe pensar acerca de los atributos: una forma de agregar datos a su código que luego podrían ser utilizados por otros métodos / clases / etc.

Bruno Brant
fuente
¿Realmente me gustará la respuesta y especialmente ", una pregunta más que puedo poner en la misma condición en la declaración de conjunto del código anterior y en qué se diferencia de los atributos,
barra oblicua
1
@slash: ¿Puedes reformular eso? No entendí bien la pregunta.
Bruno Brant
1
Creo que la barra inclinada pretendía preguntar sobre la diferencia entre usar atributos y poner el código de validación real dentro del establecedor de propiedades. Respuesta: Si bien se puede escribir código dentro del establecedor para validar el valor, el uso de atributos por sí solo no realizará la validación como tal. Los atributos son simplemente "metadatos". Otro código en otro lugar debería estar interesado en los atributos que usa, leerlos y realizar acciones basadas en ellos. Un ejemplo típico es una biblioteca de validación, como mencionó @BrunoBrant.
romar
10
No estoy seguro de por qué esta es la respuesta aceptada. La pregunta real (que también está indexada en Google) es "Cómo crear un atributo personalizado en C #". Las respuestas no ahondan en absoluto en ese tema. La segunda respuesta lo hace, por otro lado.
Drakestar
Creo que la segunda respuesta está más relacionada con la pregunta.
Mohammad Taherian
267

Empiece escribiendo una clase que se derive de Attribute :

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

Entonces podrías decorar cualquier cosa (clase, método, propiedad, ...) con este atributo:

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

}

y finalmente usarías la reflexión para buscarlo:

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
}

Puede limitar los tipos de destino a los que se puede aplicar este atributo personalizado mediante el atributo AttributeUsage :

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

Cosas importantes que debe saber sobre los atributos:

  • Los atributos son metadatos.
  • Se incorporan al ensamblado en tiempo de compilación, lo que tiene serias implicaciones sobre cómo se pueden establecer sus propiedades. Solo se aceptan valores constantes (conocidos en el momento de la compilación)
  • La única forma de tener algún sentido y uso de atributos personalizados es usar Reflection . Entonces, si no usa la reflexión en tiempo de ejecución para buscarlos y decorar algo con un atributo personalizado, no espere que suceda mucho.
  • El momento de creación de los atributos no es determinista. El CLR crea una instancia de ellos y no tiene absolutamente ningún control sobre ellos.
Darin Dimitrov
fuente
3
¿Dónde, en qué función / clase, debo `usar la reflexión para buscarla`
Hasan A Yousef
@Hasan A Yousef, por ejemplo, en Entity Framework hay un atributo "Clave" que dice al marco: Esta propiedad debe considerarse como Clave principal. Al crear ORM, los atributos son muy útiles
Parsa
¿Cómo se accede a un atributo personalizado en una propiedad y no en una clase?
Lienzo
docs.microsoft.com/en-us/dotnet/standard/attributes/… solo para completar, esta página de msdn lo resume muy bien
Barış Akkurt
Con los genéricos, que es mucho, mucho más fácil conseguir los tipos:var value = typeof(Foo).GetCustomAttributes<MyCustomAttribute>().First().SomeProperty;
jpaugh
27

Utilizando / copiando la gran respuesta de Darin Dimitrov , así es como acceder a un atributo personalizado en una propiedad y no en una clase:

La propiedad decorada [de clase Foo]:

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

Obteniéndolo:

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

También puede lanzar esto en un bucle y usar la reflexión para acceder a este atributo personalizado en cada propiedad de la clase Foo:

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

Muchas gracias a ti, Darin !!

Tolva
fuente
¿Cómo ampliaríamos esto si no sabemos qué tipos de atributos existen en una propiedad? object[] attribute = propertyInfo.GetCustomAttributes(typeof(???), true);Solo quiero iterar sobre todos ellos y llamar a un método m1()de cada atributo desconocido
heyNow
0

La respuesta corta es para crear un atributo en c #, solo necesita heredarlo de la clase Attribute, solo esto :)

Pero aquí voy a explicar los atributos en detalle:

básicamente los atributos son clases que podemos usarlos para aplicar nuestra lógica a ensamblajes, clases, métodos, propiedades, campos, ...

En .Net, Microsoft ha proporcionado algunos atributos predefinidos como Obsolete o Validation Attributes como ([Required], [StringLength (100)], [Range (0, 999.99)]), también tenemos atributos como ActionFilters en asp.net que puede ser muy útil para aplicar nuestra lógica deseada a nuestros códigos (lea este artículo sobre filtros de acción si le apasiona aprenderlo)

otro punto, puede aplicar un tipo de configuración en su atributo a través de AttibuteUsage.

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

Cuando decora una clase de atributo con AttributeUsage, puede decirle al compilador de c # dónde voy a usar este atributo: voy a usar esto en clases, en ensamblajes en propiedades o en ... y mi atributo puede usar varias veces en objetivos definidos (clases, ensamblajes, propiedades, ...) o no ?!

Después de esta definición sobre atributos, les voy a mostrar un ejemplo: Imagine que queremos definir una nueva lección en la universidad y queremos permitir que solo los administradores y maestros de nuestra universidad definan una nueva lección, ¿de acuerdo?

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


}

En el mundo real de la programación, tal vez no usemos este enfoque para usar atributos y lo dije por su punto educativo en el uso de atributos.

Hamid
fuente