2012-06-12 15 views
10

Se es posible hacer esto:Atributo Constructor Con Lambda

public static void SomeMethod<TFunc>(Expression<TFunc> expr) 
{ 
    //LambdaExpression happily excepts any Expession<TFunc> 
    LambdaExpression lamb = expr; 
} 

y lo llaman en otros lugares pasando una lambda para el parámetro:

SomeMethod<Func<IQueryable<Person>,Person>>(p=>p.FirstOrDefault()); 

lugar me gustaría pasar una expresión como un parámetro para un constructor de atributo . ¿Es posible hacer lo siguiente?

class ExpandableQueryAttribute: Attribute { 
    private LambdaExpression someLambda; 
    //ctor 
    public ExpandableQueryMethodAttribute(LambdaExpression expression) 
    { 
     someLambda = expression 
    } 
} 

//usage: 
static LambdaExpression exp = 
     (Expression<Func<IQueryable<Person>, Person>>) 
     (p => p.FirstOrDefault()); 

[ExpandableQueryAttribute(exp)] //error here 
// "An attribute argument must be a constant expression, typeof expression 
// or array creation expression of an attribute parameter type" 

Mi objetivo es especificar un método o lambda en el constructor del atributo (aunque tenga que declarar un método denominado completa y pasar el nombre del método de alguna manera, que estaría bien a) .

  1. tipos de parámetros pueden cambiar, pero es importante que el constructor atributo puede tomar ese parámetro y de alguna manera poder asignarlo a un campo de tipo LambdaExpression

  2. Quiero que la declaración de la lambda/method está justo encima de la llamada al constructor del atributo, o en línea, para que no tenga que ir muy lejos para ver qué se está pasando.

Así que estas alternativas no habría problema, pero sin suerte conseguir que funcionen:

public static ... FuncName(...){...} 

[ExpandableQueryAttribute(FuncName)] 
// ... 

o

//lambdas aren't allowed inline for an attribute, as far as I know 
[ExpandableQueryAttribute(q => q.FirstOrDefault())] 
// ... 

El trabajo existente en torno es pasar un número de identificación al constructor (satisfaciendo el requisito de "argumento debe ser una constante"), que es utilizado por el constructor para hacer una búsqueda en un diccionario donde las expresiones se han agregado previamente. Estaba esperando mejorar/simplificar esto, pero tengo la sensación de que no mejora debido a las limitaciones en los constructores de atributos.

+0

Siga su sentimiento ... la limitación de argumento atributo es bastante clara. –

+1

La pregunta también se hizo en este enlace. La respuesta fue que actualmente no es posible. http://social.msdn.microsoft.com/Forums/en/vcsharp2008prerelease/thread/0d18c410-07b0-41cc-9c7f-9494633ca101 – Jamey

+0

@Jamey Sí, esa es la última alternativa que enumeré, que sabía que era una limitación. Tenía la esperanza de evitarlo al declarar la expresión como una variable, pero luego el requisito "debe ser constante" me atrapó. La solución alternativa allí es interesante, y voy a probar una variación de eso. – AaronLS

Respuesta

7

¿qué tal esto:

class ExpandableQueryAttribute : Attribute 
    { 

     private LambdaExpression someLambda; 
     //ctor 
     public ExpandableQueryAttribute(Type hostingType, string filterMethod) 
     { 
      someLambda = (LambdaExpression)hostingType.GetField(filterMethod).GetValue(null); 
      // could also use a static method 
     } 
    } 

esto debería permitir que se asigna a su lambda a un campo y luego se aspira en tiempo de ejecución, aunque en general yo preferiría usar algo como PostSharp hacer esto en tiempo de compilación .

ejemplo de uso sencillo

public class LambdaExpressionAttribute : Attribute 
    { 
     public LambdaExpression MyLambda { get; private set; } 
     //ctor 
     public LambdaExpressionAttribute(Type hostingType, string filterMethod) 
     { 
      MyLambda = (LambdaExpression)hostingType.GetField(filterMethod).GetValue(null); 
     } 
    } 

    public class User 
    { 
     public bool IsAdministrator { get; set; } 
    } 

    public static class securityExpresions 
    { 
     public static readonly LambdaExpression IsAdministrator = (Expression<Predicate<User>>)(x => x.IsAdministrator); 
     public static readonly LambdaExpression IsValid = (Expression<Predicate<User>>)(x => x != null); 

     public static void CheckAccess(User user) 
     { 
      // only for this POC... never do this in shipping code 
      System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(); 
      var method = stackTrace.GetFrame(1).GetMethod(); 

      var filters = method.GetCustomAttributes(typeof(LambdaExpressionAttribute), true).OfType<LambdaExpressionAttribute>(); 
      foreach (var filter in filters) 
      { 
       if ((bool)filter.MyLambda.Compile().DynamicInvoke(user) == false) 
       { 
        throw new UnauthorizedAccessException("user does not have access to: " + method.Name); 
       } 
      } 

     } 
    } 

    public static class TheClass 
    { 
     [LambdaExpression(typeof(securityExpresions), "IsValid")] 
     public static void ReadSomething(User user, object theThing) 
     { 
      securityExpresions.CheckAccess(user); 
      Console.WriteLine("read something"); 
     } 

     [LambdaExpression(typeof(securityExpresions), "IsAdministrator")] 
     public static void WriteSomething(User user, object theThing) 
     { 
      securityExpresions.CheckAccess(user); 
      Console.WriteLine("wrote something"); 
     } 

    } 


    static void Main(string[] args) 
    { 

     User u = new User(); 
     try 
     { 
      TheClass.ReadSomething(u, new object()); 
      TheClass.WriteSomething(u, new object()); 
     } 
     catch(Exception e) 
     { 
      Console.WriteLine(e); 
     } 
    } 
+0

podría agregar un ejemplo de uso? votó con anticipación :) –

+0

@ChrisMcCall agregó un ejemplo simple – Yaur

+0

ok Lo entiendo ahora, declarando la lambda en otro lugar y refiriéndome a ella por su nombre ... ¡listo! –

3

Esto no es posible porque lo que puede pasar a un atributo debe caber en el formato DLL binario de CLR y no hay forma de codificar la inicialización de objetos arbitraria. Por lo mismo, no puede pasar un valor que se puede nulos, por ejemplo. Las restricciones son muy estrictas

0

Aunque no puede tener un constructor complejo para los atributos, en algunas situaciones un sonido de trabajo debe tener una propiedad pública para ese atributo y actualizarlo en tiempo de ejecución.

apuntando a un objeto de la clase que contiene algunos atributos en sus propiedades. LocalDisplayNameAttribute es un atributo personalizado.

El siguiente código establecerá la propiedad ResourceKey de mi clase de atributo personalizada en tiempo de ejecución. Luego, en ese momento, puede anular DisplayName para que salga el texto que desee.

 static public void UpdateAttributes(object self) 
    { 
     foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(self)) 
     { 
      LocalDisplayNameAttribute attr = 
       prop.Attributes[typeof(LocalDisplayNameAttribute)] 
        as LocalDisplayNameAttribute; 

      if (attr == null) 
      { 
       continue; 
      } 

      attr.ResourceKey = prop.Name; 
     } 
    } 
Cuestiones relacionadas