¿Cómo obtengo el miembro al que se aplicó mi atributo personalizado?


Estoy creando un atributo personalizado en C# y quiero hacer cosas diferentes según si el atributo se aplica a un método o a una propiedad. Al principio iba a hacer new StackTrace().GetFrame(1).GetMethod() en mi constructor de atributos personalizado para ver qué método llamó al constructor de atributos, pero ahora no estoy seguro de lo que eso me dará. ¿Qué pasa si el atributo se aplica a una propiedad? ¿Devolvería GetMethod() una instancia MethodBase para esa propiedad? ¿Hay una manera diferente de conseguir el miembro a la que un ¿el atributo se aplicó en C#?

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

Actualización: Bien, podría haber estado haciendo la pregunta equivocada. Desde dentro de una clase de atributo personalizado, ¿cómo obtengo el miembro (o la clase que contiene el miembro) al que se aplicó mi atributo personalizado? Aaronaght sugirió no subir por la pila para encontrar el miembro de la clase al que se aplicó mi atributo, pero ¿de qué otra manera obtendría esta información dentro del constructor de mi atributo?

Author: Community, 2010-01-30

4 answers

Dado que parece haber mucha confusión con respecto a cómo funcionan los marcos de pila y los métodos, aquí hay una demostración 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;
        }
    }
}

La salida de este programa será:

Set_Name

Las propiedades en C# son una forma de azúcar sintáctico. Compilan los métodos getter y setter en el IL, y es posible que algunos lenguajes. NET ni siquiera los reconozcan como propiedades-la resolución de propiedades se hace completamente por convención, no hay realmente cualquier regla en el IL spec.

Ahora, digamos por el momento que tienes una muy buena razón para que un programa quiera examinar su propia pila (y hay muy pocas razones prácticas para hacerlo). ¿Por qué en el mundo querría que se comportara de manera diferente para las propiedades y los métodos?

La razón detrás de los atributos es que son una especie de metadatos. Si desea un comportamiento diferente, codifíquelo en el atributo. Si un atributo puede significar dos cosas diferentes dependiendo de si se aplica a un método o propiedad, entonces usted debe tener dos atributos. Establecer el objetivo en el primero a AttributeTargets.Method y el segundo a AttributeTargets.Property. Simple.

Pero una vez más, caminar por su propia pila para recoger algunos atributos del método de llamada es peligroso en el mejor de los casos. En cierto modo, está congelando el diseño de su programa, lo que hace que sea mucho más difícil para cualquier persona extender o refactorizar. Esta no es la forma en que los atributos se usan normalmente. A más ejemplo apropiado, sería algo así como un atributo de validación:

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

Entonces su código validador, que no sabe nada sobre la entidad real que se está pasando, puede hacer esto:

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 otras palabras, estás examinando los atributos de una instancia que te fue dada pero que no necesariamente sabes nada sobre el tipo de. Atributos XML, atributos de contrato de datos, incluso atributos de atributos: casi todos los atributos en. NET Framework son utilizado de esta manera, para implementar alguna funcionalidad que es dinámica con respecto al tipo de una instancia pero no con respecto al estado del programa o lo que pasa a estar en la pila. Es muy poco probable que realmente esté en control de esto en el punto donde crea el seguimiento de la pila.

Así que voy a recomendar de nuevo que no use el enfoque de caminar sobre la pila a menos que tenga una muy buena razón para hacerlo que no nos haya dicho ya casi. De lo contrario, es probable que se encuentre en un mundo de dolor.

Si es absolutamente necesario (no diga que no le advertimos), use dos atributos, uno que se puede aplicar a los métodos y otro que se puede aplicar a las propiedades. Creo que encontrarás que es mucho más fácil trabajar con eso que con un solo súper atributo.

 40
Author: Aaronaught,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-09 18:19:13

Los atributos proporcionan metadatos y no saben nada sobre la cosa (clase, miembro, etc. están decorando. Por otro lado, la cosa que está siendo decorada puede pedir los atributos con los que está decorada.

Si debe saber el tipo de la cosa que se está decorando, necesitará pasarlo explícitamente a su atributo en su constructor.

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

   public MyCustomAttribute(Type type)
   {
      this.type = type;
   }
}
 39
Author: Scott Dorman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-30 20:47:37

Los atributos personalizados se activan mediante un código que llama al método GetCustomAttributes en el ICustomAttributeProvider (objeto de reflexión) que representa la ubicación donde se aplica el atributo. Así que en el caso de una propiedad, algún código obtendría el PropertyInfo para la propiedad y luego llamaría a GetCustomAttributes en eso.

Si desea construir algún marco de validación, debería escribir el código que inspecciona los tipos y miembros para obtener atributos personalizados. Usted podría para ejemplo tiene una interfaz que los atributos implementan para participar en su marco de validación. Podría ser tan simple como lo siguiente:

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

Su código podría buscar esta inteface en (por ejemplo) un tipo:

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

(presumiblemente recorrería todo el gráfico de reflexión y haría esto para cada ICustomAttributeProvider). Para un ejemplo de un enfoque similar en acción en el FX. net, puede ver los 'comportamientos' de WCF (IServiceBehavior, IOperationBehavior, etc.).

Actualizar: el. net FX tiene una especie de propósito general, pero básicamente un marco de interceptación indocumentado en forma de ContextBoundObject y ContextAttribute. Puede buscar en la web algunos ejemplos de cómo usarlo para AOP.

 4
Author: alexdej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-30 19:16:42

GetMethod siempre le devolverá el nombre de la función. Si es una propiedad, obtendrá get_PropertyName o set_PropertyName.

Una propiedad es básicamente un tipo de método, por lo que cuando implementa una propiedad, el compilador crea dos funciones separadas en el MSIL resultante, a get_ y a set_. Esta es la razón por la que en el stack trace recibes estos nombres.

 4
Author: Amirshk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-02 22:32:35