【问题标题】:Attribute Constructor With Lambda使用 Lambda 的属性构造函数
【发布时间】:2012-06-15 19:44:04
【问题描述】:

可以这样做:

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 传递给构造函数(满足“参数必须是常量”的要求),构造函数使用它在先前添加了表达式的字典中进行查找。希望改进/简化这一点,但我觉得由于属性构造函数的限制,它并没有变得更好。

【问题讨论】:

标签: c# lambda custom-attributes


【解决方案1】:

这个怎么样:

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

这应该让您将 lambda 分配给一个字段,然后在运行时将其吸入,尽管通常我更喜欢使用 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);
        }
    }

【讨论】:

  • @ChrisMcCall 添加了一个简单的例子
  • 好的我现在明白了,在其他地方声明 lambda 并用它的名字来引用它......聪明!
【解决方案2】:

这是不可能的,因为您可以传递给属性的内容需要适合 CLR 的二进制 DLL 格式,并且无法对任意对象初始化进行编码。同样,您不能传递一个可为空的值。限制非常严格。

【讨论】:

    【解决方案3】:

    虽然您不能为属性创建复杂的构造函数,但在某些情况下,解决方法是为该属性创建一个公共属性并在运行时对其进行更新。

    self 指向一个类的对象,该对象在其属性上包含一些属性。 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;
            }
        }
    

    【讨论】:

      【解决方案4】:

      使用动态 linq:http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

      您可以使属性的构造函数采用一个字符串,该字符串的计算结果为表达式。

      【讨论】:

        猜你喜欢
        • 2013-05-24
        • 1970-01-01
        • 2022-12-16
        • 2023-04-02
        • 2016-12-14
        • 1970-01-01
        • 1970-01-01
        • 2018-06-11
        • 1970-01-01
        相关资源
        最近更新 更多