Atributo Constructor Con Lambda

Es posible hacer esto:

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

y llámalo a otro lugar pasando un lambda para el parámetro:

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

En cambio, me gustaría pasar una expresión como un parámetro a un constructor de atributos . ¿Es posible hacer lo siguiente?

 class ExpandableQueryAttribute: Attribute { private LambdaExpression someLambda; //ctor public ExpandableQueryMethodAttribute(LambdaExpression expression) { someLambda = expression } } //usage: static LambdaExpression exp = (Expression<Func<IQueryable, 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" 

Mi objective es especificar un método o lambda en el constructor del atributo (incluso si tengo que declarar un método con nombre completo y pasar el nombre del método de alguna manera, estaría bien).

  1. Los tipos de parámetros pueden cambiar, pero es importante que el constructor de atributos pueda tomar ese parámetro y de alguna manera poder asignarlo a un campo de tipo LambdaExpression

  2. Quiero que la statement de la lambda / método esté justo por encima de la llamada al constructor de atributos, o en línea, para que no tenga que ir muy lejos para ver qué se está pasando.

Así que estas alternativas estarían bien, pero sin suerte hacer que funcionen:

 public static ... FuncName(...){...} [ExpandableQueryAttribute(FuncName)] // ... 

o

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

La solución existente es pasar un ID de número al constructor (cumplir con el requisito de “el argumento debe ser una constante”), que el constructor utiliza para realizar una búsqueda en un diccionario donde se han agregado expresiones previamente. Esperaba mejorar / simplificar esto, pero tengo la sensación de que no mejora nada debido a las limitaciones en los constructores de atributos.

Qué tal esto:

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

esto debería permitirle asignar su lambda a un campo y luego absorberlo en el tiempo de ejecución, aunque en general preferiría usar algo como PostSharp para hacer esto en tiempo de comstackción.

ejemplo de uso simple

  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>)(x => x.IsAdministrator); public static readonly LambdaExpression IsValid = (Expression>)(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(); 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); } } 

Esto no es posible porque lo que puede pasar a un atributo debe ajustarse al formato binario DLL de CLR y no hay forma de codificar la inicialización de objetos arbitrarios. Por lo mismo usted no puede pasar un valor anulable por ejemplo. Las restricciones son muy estrictas.

Aunque no puede tener un constructor complejo para los atributos, en algunas situaciones, una solución de trabajo es tener una propiedad pública para ese atributo y actualizarla en tiempo de ejecución.

auto apuntar a un objeto de la clase que contiene algunos atributos en sus propiedades. LocalDisplayNameAttribute es un atributo personalizado.

El siguiente código establecerá la propiedad ResourceKey de mi clase de atributo personalizado en tiempo de ejecución. Luego, en ese punto, puede anular DisplayName para salir del texto que desee.

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

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

Puede hacer que el constructor del atributo tome una cadena que se evalúe como una expresión.