c# - Atributo Constructor con Lambda
custom-attributes (4)
Esto no es posible porque lo que puede pasar a un atributo debe encajar en el formato DLL binario de CLR y no hay forma de codificar la inicialización de objetos arbitraria. Por lo mismo, no puede pasar un valor que se puede nulos, por ejemplo. Las restricciones son muy estrictas
Es posible hacer esto:
public static void SomeMethod<TFunc>(Expression<TFunc> expr)
{
//LambdaExpression happily excepts any Expession<TFunc>
LambdaExpression lamb = expr;
}
y llamarlo a otro lugar pasando un lambda por el parámetro:
SomeMethod<Func<IQueryable<Person>,Person>>( p=>p.FirstOrDefault());
En cambio, me gustaría pasar una expresión como 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>, 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 objetivo 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, eso estaría bien).
Los tipos de parámetros pueden cambiar, pero es importante que el constructor del atributo pueda tomar ese parámetro y de alguna manera poder asignarlo a un campo de tipo LambdaExpression
Quiero que la declaración del lambda / método esté justo encima de la llamada al constructor del atributo, o en línea, para que no tenga que ir muy lejos para ver qué se está pasando.
Entonces estas alternativas estarían bien, pero no hay suerte para 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 alternativa consiste en pasar una ID numérica al constructor (que satisface el requisito de "el argumento debe ser una constante"), que el constructor usa para realizar una búsqueda en un diccionario donde las expresiones se han agregado previamente. Estaba esperando mejorar / simplificar esto, pero tengo la sensación de que no mejora debido a las limitaciones en los constructores de atributos.
Utilice dymanic linq: 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úa como una expresión.
Aunque no puede tener un constructor complejo para los atributos, en algunas situaciones un work-aound debe tener una propiedad pública para ese atributo y actualizarlo en tiempo de ejecución.
auto señalar 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 momento, puede anular DisplayName para que salga el 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;
}
}
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 aspirarlo en tiempo de ejecución, aunque en general preferiría usar algo como PostSharp para hacerlo en tiempo de compilació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<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);
}
}