2012-06-12 83 views
10

可能做到這一點:屬性構造和λ

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

,並把它在其他地方傳遞一個lambda爲參數:

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

我反而想傳遞一個表達式參數屬性構造函數是否可以做到以下幾點?

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" 

我的目標是到指定屬性的構造方法或lambda(即使我不得不宣佈一個完整的命名方法,並以某種方式傳遞方法的名稱,我不會有事來) 。

  1. 參數類型可以改變,但它是非常重要的屬性構造可以採取的參數,並以某種方式能夠將它分配給類型LambdaExpression的領域

  2. 我想要的聲明lambda /方法只是在對屬性構造函數的調用之上,或者是內聯的,這樣你就不必走很遠才能看到傳遞的內容。

所以這些替代品將被罰款,但沒有運氣讓他們工作:

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

[ExpandableQueryAttribute(FuncName)] 
// ... 

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

現有的解決辦法是一些ID傳遞給構造函數(滿足「參數必須是一個常量」的要求),構造函數使用該參數在字典中查找以前添加了表達式的參數。希望能夠改進/簡化這一點,但是我有一種感覺,由於對屬性構造函數的限制,它並沒有得到任何改善。

+0

按照你的感覺......對屬性參數的限制相當明確。 –

+1

在這個鏈接也提出了問題。答案是目前不可能。 http://social.msdn.microsoft.com/Forums/en/vcsharp2008prerelease/thread/0d18c410-07b0-41cc-9c7f-9494633ca101 – Jamey

+0

@Jamey是的,這是我列出的最後一個選擇,我知道是一個限制。希望通過將表達式聲明爲一個變量來解決這個問題,但是這個「必須是不變的」要求讓我受益匪淺。這個解決方法雖然很有意思,但我會嘗試一種變化。 – AaronLS

回答

7

這個怎麼樣:

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

這應該讓你的拉姆達分配給字段,然後在運行時,吸盡管通常我更願意使用像PostSharp在編譯的時候要做到這一點。

簡單的使用例子

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

可以添加嗎?一個使用示例?投票預計:) –

+0

@ChrisMcCall添加了一個簡單的例子 – Yaur

+0

好吧我現在明白了,在其他地方聲明lambda並通過它的名字引用它......聰明! –

3

這是不可能的,因爲你可以傳入一個屬性需要適合CLR的二進制DLL格式,並且沒有辦法編碼任意對象初始化。例如,對於同樣的你,你不能傳遞一個可爲空的值。限制非常嚴格。

0

儘管您不能擁有複雜的屬性構造函數,但在某些情況下,工作方式是爲該屬性擁有公共屬性並在運行時對其進行更新。

自指向包含其屬性某些屬性的類的對象。 LocalDisplayNameAttribute是一個自定義屬性。

以下代碼將在運行時設置我的自定義屬性類的ResourceKey屬性。然後在那個時候,你可以覆蓋DisplayName來取代你想要的任何文本。

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