2012-06-12 15 views
10

E è possibile fare questo:Attributo costruttore con Lambda

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

e chiamarlo passando altrove un lambda per il parametro:

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

avrei invece vuole passare un'espressione come un parametro per un costruttore di attributi . È possibile fare il seguito?

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" 

Il mio obiettivo è quello di indicare un metodo o lambda nel costruttore dell'attributo (anche se devo dichiarare un metodo chiamato pieno e passare il nome del metodo in qualche modo, che sarebbe bene per) .

  1. Tipi di parametri possono cambiare, ma è importante che il costruttore attributo può prendere quel parametro e in qualche modo essere in grado di assegnarlo a un campo di tipo LambdaExpression

  2. Voglio la dichiarazione del lambda/metodo deve essere appena sopra la chiamata al costruttore di attributi, o inline, in modo da non dover andare lontano per vedere cosa viene passato.

Quindi queste alternative andrebbe bene, ma senza fortuna inducendole a lavorare:

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

[ExpandableQueryAttribute(FuncName)] 
// ... 

o

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

Il lavoro esistente intorno è quello di passare un numero ID al costruttore (soddisfare il requisito "l'argomento deve essere costante"), che viene utilizzato dal costruttore per eseguire una ricerca in un dizionario in cui le espressioni sono state aggiunte in precedenza. Speravo di migliorare/semplificare questo, ma ho la sensazione che non migliori grazie alle limitazioni sui costruttori di attributi.

+0

Segui il tuo sentimento ... la limitazione sull'argomento attributo è piuttosto chiara. –

+1

La domanda è stata anche posta a questo link. La risposta è stata che non è attualmente possibile. http://social.msdn.microsoft.com/Forums/en/vcsharp2008prerelease/thread/0d18c410-07b0-41cc-9c7f-9494633ca101 – Jamey

+0

@Jamey Sì, questa è l'ultima alternativa che ho elencato che sapevo essere una limitazione. Speravo di aggirare questo dichiarando l'espressione come una variabile, ma poi il requisito "deve essere costante" mi ha preso. Comunque, la soluzione è interessante, e ho intenzione di provare una variazione di questo. – AaronLS

risposta

7

come su questo:

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

questo dovrebbe permettere di assegnare il vostro lambda a un campo e poi succhiare in in fase di esecuzione, anche se in generale io preferirei usare qualcosa come PostSharp per fare questo al momento della compilazione .

esempio di semplice utilizzo

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

potresti aggiungere un esempio di utilizzo? up votato in anticipo :) –

+0

@ChrisMcCall ha aggiunto un semplice esempio – Yaur

+0

ok Ho capito ora, dichiarando la lambda da qualche altra parte e riferendomi con il suo nome ... intelligente! –

3

Questo non è possibile perché ciò che è possibile passare in un attributo deve rientrare nel formato DLL binario del CLR e non è possibile codificare l'inizializzazione dell'oggetto arbitrario. Per lo stesso tu non puoi passare un valore nullable per esempio. Le restrizioni sono molto severe.

0

Sebbene non sia possibile avere un costruttore complesso per gli attributi, in alcune situazioni un lavoro è la proprietà pubblica di tale attributo e l'aggiornamento in fase di esecuzione.

self point a un oggetto della classe che contiene alcuni attributi sulle sue proprietà. LocalDisplayNameAttribute è un attributo personalizzato.

Il seguente codice imposterà la proprietà ResourceKey della mia classe di attributo personalizzata in fase di esecuzione. Quindi a quel punto puoi sovrascrivere DisplayName per superare qualsiasi testo tu voglia.

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