11

これを行うこと可能です:

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

パラメータのラムダを渡して他の場所で呼び出します。

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"

私の目標は、属性のコンストラクターでメソッドまたはラムダを指定することです(完全な名前のメソッドを宣言し、メソッドの名前をなんらかの方法で渡す必要がある場合でも、それで問題ありません)。

  1. パラメーターの型は変更できますが、属性コンストラクターがそのパラメーターを受け取り、何らかの方法でLambdaExpression型のフィールドに割り当てることができることが重要です。

  2. ラムダ/メソッドの宣言を属性コンストラクターの呼び出しのすぐ上、つまりインラインにして、何が渡されているかを確認するために遠くまで行く必要がないようにします。

したがって、これらの代替手段は問題ありませんが、それらを機能させることはできません。

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

[ExpandableQueryAttribute(FuncName)]   
// ...

また

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

既存の回避策は、コンストラクターに数値IDを渡すことです(「引数は定数でなければならない」要件を満たします)。これは、以前に式が追加された辞書でルックアップを実行するためにコンストラクターによって使用されます。これを改善/簡素化することを望んでいましたが、属性コンストラクターの制限により、これ以上改善されないと感じています。

4

4 に答える 4

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);
        }
    }
于 2012-06-12T22:11:42.710 に答える
3

属性に渡すことができるものは、CLR のバイナリ DLL 形式に適合する必要があり、任意のオブジェクトの初期化をエンコードする方法がないため、これは不可能です。同じために、たとえば null 許容値を渡すことはできません。制限は非常に厳しいです。

于 2012-06-12T21:41:13.470 に答える
0

属性の複雑なコンストラクターを持つことはできませんが、状況によっては、その属性のパブリック プロパティを持ち、実行時に更新することで解決できます。

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;
        }
    }
于 2014-10-01T15:02:44.827 に答える
-1

動的リンクを使用: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

属性のコンストラクターが式に評価される文字列を取るようにすることができます。

于 2012-06-17T15:00:46.623 に答える