属性构造器和λ(Attribute Constructor With Lambda)

2019-06-25 10:21发布

可能做到这一点:

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传递给构造(满足“参数必须是一个常数”的规定),其用于由构造做一个查找在表达式之前已经加入的字典。 希望能改进/简化了这一点,但我有一种感觉它没有得到任何的限制,由于更好的属性构造。

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

这应该让你的拉姆达分配到一个区域,然后它在运行时的吮吸,但一般我宁愿使用像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);
        }
    }


Answer 2:

这是不可能的,因为你可以传递到属性需要融入CLR的二进制DLL格式,也没有办法进行编码任意对象的初始化。 出于同样的,你不能传递例如可为空值。 这些限制是非常严格的。



Answer 3:

虽然你不能有属性的复杂构造,在某些情况下工作,风靡是拥有该属性的公共财产,并在运行时更新。

自我指向包含在其属性的某些属性的类的对象。 LocalDisplayNameAttribute是一个自定义属性。

下面的代码将在运行时设置我的自定义属性类的ResourceKey属性。 然后,在这一点上,你可以覆盖到显示名称outpiut任何你想要的文字。

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


Answer 4:

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

您可以将属性的构造需要将计算得到的表达式的字符串。



文章来源: Attribute Constructor With Lambda