2012-06-12 12 views
10

To jest możliwe, aby to zrobić:Atrybut konstruktor Lambda

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

i nazywają to gdzie indziej przechodzącą lambda dla parametru:

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

bym zamiast chciał przekazać wyrażenie jako parametr do konstruktora atrybutów. Czy jest możliwe aby wykonać poniżej?

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" 

Moim celem jest określenie sposobu lub lambda w konstruktorze atrybutu (nawet jeśli będę musiał zadeklarować pełną metodę o nazwie i przekazać nazwę metody jakoś, że byłoby dobrze, aby) .

  1. rodzaje parametrów mogą się zmienić, ale ważne jest, że konstruktor atrybut może mieć ten parametr i w jakiś sposób móc przypisać go do pola typu LambdaExpression

  2. Chcę deklarację lambda/metoda się tuż nad wywołaniem konstruktora atrybutu lub inline, tak że nie trzeba iść daleko, by zobaczyć, co jest przekazywane.

Więc te alternatywy byłoby w porządku, ale bez powodzenia zachęcając do pracy:

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

[ExpandableQueryAttribute(FuncName)] 
// ... 

lub

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

Dotychczasowa praca wokół jest przekazać identyfikator numeryczny do konstruktora (spełniająca „argumentu musi być stała” wymóg), który jest używany przez konstruktora wykonać wyszukiwanie w słowniku gdzie wyrażenia zostały dodane wcześniej. Miałem nadzieję poprawić/uprościć to, ale mam wrażenie, że nie ma nic lepszego z powodu ograniczeń konstruktorów atrybutów.

+0

Podążaj za swoim uczuciem ... ograniczenie argumentu atrybutu jest raczej jasne. –

+1

Pytanie zostało również zadane pod tym linkiem. Odpowiedź była taka, że ​​obecnie nie jest to możliwe. http://social.msdn.microsoft.com/Forums/en/vcsharp2008prerelease/thread/0d18c410-07b0-41cc-9c7f-9494633ca101 – Jamey

+0

@Jamey Yep, jest to ostatnia alternatywa, którą wymieniłem, co do którego wiedziałem, że jest ograniczeniem. Miałem nadzieję, że obejdzie to zadanie, deklarując wyrażenie jako zmienną, ale wtedy otrzymałem wymóg "musi być stały". Obejście problemu jest interesujące, ale spróbuję tego zmienić. – AaronLS

Odpowiedz

7

jak o tym:

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

powinno to pozwalają przypisać lambda na polu, a potem ssać go w czasie wykonywania, choć generalnie wolałbym użyć czegoś podobnego PostSharp to zrobić w czasie kompilacji .

prosty przykład użycia

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

czy mógłbyś dodać przykład użycia? w głosowaniu w oczekiwaniu :) –

+0

@ChrisMcCall dodał prosty przykład – Yaur

+0

ok Otrzymuję go teraz, deklarując lambdę gdzieś indziej i odnosząc się do niej po imieniu ... sprytny! –

3

To nie jest możliwe, ponieważ to, co można przekazać do atrybut musi pasować do formatu binarnego DLL przez CLR i nie ma sposobu, aby zakodować dowolny inicjalizacji obiektu. Dla tego samego nie można na przykład podać wartości zerowej. Ograniczenia są bardzo surowe.

0

Choć nie można mieć złożoną konstruktora dla atrybutów, w niektórych sytuacjach praca-aound ma mieć własności publicznej dla tego atrybutu i aktualizuje je w czasie wykonywania.

własny punkt do obiektu klasy, który zawiera niektóre atrybuty na jego właściwości. LocalDisplayNameAttribute jest niestandardowym atrybutem.

Poniższy kod ustawi właściwość ResourceKey dla mojej niestandardowej klasy atrybutów w czasie wykonywania. W tym momencie możesz zastąpić DisplayName, aby wyprzedzić dowolny tekst.

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