c# - son - que es un atributo en visual basic
Expresión Lambda en el constructor de atributos (6)
No puedes
- no puede crear tipos de atributos genéricos (simplemente no está permitido); igualmente, no se define ninguna sintaxis para usar atributos genéricos (
[Foo<SomeType>]
) - no puede usar lambdas en los inicializadores de atributos; los valores disponibles para pasar a los atributos son muy limitados, y simplemente no incluye expresiones (que son muy complejas y son objetos en tiempo de ejecución, no literales en tiempo de compilación)
RelatedPropertyAttribute
una clase de Attribute
llamada RelatedPropertyAttribute
:
[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute: Attribute
{
public string RelatedProperty { get; private set; }
public RelatedPropertyAttribute(string relatedProperty)
{
RelatedProperty = relatedProperty;
}
}
Lo uso para indicar propiedades relacionadas en una clase. Ejemplo de cómo lo usaría:
public class MyClass
{
public int EmployeeID { get; set; }
[RelatedProperty("EmployeeID")]
public int EmployeeNumber { get; set; }
}
Me gustaría usar expresiones lambda para poder pasar un tipo fuerte al constructor de mi atributo, y no a una "cadena mágica". De esta forma puedo explotar la verificación del tipo de compilador. Por ejemplo:
public class MyClass
{
public int EmployeeID { get; set; }
[RelatedProperty(x => x.EmployeeID)]
public int EmployeeNumber { get; set; }
}
Pensé que podría hacerlo con lo siguiente, pero no está permitido por el compilador:
public RelatedPropertyAttribute<TProperty>(Expression<Func<MyClass, TProperty>> propertyExpression)
{ ... }
Error:
El tipo no genérico ''RelatedPropertyAttribute'' no se puede usar con argumentos de tipo
¿Cómo puedo conseguir esto?
No puedes. Los tipos de atributos están limitados según lo escrito here . Mi sugerencia, trate de evaluar su expresión lambda externamente, luego use uno de los siguientes tipos:
- Tipos simples (bool, byte, char, corto, int, largo, flotante y doble)
- cuerda
- Tipo de sistema
- enums
- objeto (El argumento para un parámetro de atributo de tipo objeto debe ser un valor constante de uno de los tipos anteriores).
- Arrays unidimensionales de cualquiera de los tipos anteriores
Para ampliar mi comentario , esta es una forma de lograr su tarea con un enfoque diferente. Usted dice que quiere "indicar propiedades relacionadas en una clase", y que "le gustaría usar expresiones lambda para poder pasar un tipo fuerte al constructor de mi atributo y no a una" cadena mágica ". De esta forma puedo explotar comprobación del tipo de compilador ".
Aquí hay una forma de indicar propiedades relacionadas que están escritas en tiempo de compilación y que no tienen ninguna cadena mágica:
public class MyClass
{
public int EmployeeId { get; set; }
public int EmployeeNumber { get; set; }
}
Esta es la clase en consideración. Queremos indicar que EmployeeId
y EmployeeNumber
están relacionados. Para obtener un poco de concisión del código, pongamos este alias de tipo en la parte superior del archivo de código. No es necesario en absoluto, pero hace que el código sea menos intimidante:
using MyClassPropertyTuple =
System.Tuple<
System.Linq.Expressions.Expression<System.Func<MyClass, object>>,
System.Linq.Expressions.Expression<System.Func<MyClass, object>>
>;
Esto hace que MyClassPropertyTuple
sea un alias para una Tuple
de dos Expression
, cada una de las cuales captura la definición de una función de una MyClass
en un objeto. Por ejemplo, los captadores de propiedades en MyClass
son tales funciones.
Ahora capturemos la relación. Aquí hice una propiedad estática en MyClass
, pero esta lista podría definirse en cualquier lugar:
public class MyClass
{
public static List<MyClassPropertyTuple> Relationships
= new List<MyClassPropertyTuple>
{
new MyClassPropertyTuple(c => c.EmployeeId, c => c.EmployeeNumber)
};
}
El compilador de C # sabe que estamos construyendo un Tuple
of Expression
s, por lo que no necesitamos ningún molde explícito frente a esas expresiones lambda; se convierten automáticamente en Expression
s.
Eso es básicamente en términos de definición: esas menciones EmployeeId
y EmployeeNumber
son fuertemente tipadas y aplicadas en tiempo de compilación, y las herramientas de refactorización que cambian los nombres de las propiedades deberían ser capaces de encontrar esos usos durante un cambio de nombre (ReSharper definitivamente puede). No hay cuerdas mágicas aquí.
Pero, por supuesto, también queremos ser capaces de interrogar las relaciones en tiempo de ejecución (¡supongo!). No sé exactamente cómo quieres estar haciendo esto, así que este código es solo ilustrativo.
class Program
{
static void Main(string[] args)
{
var propertyInfo1FromReflection = typeof(MyClass).GetProperty("EmployeeId");
var propertyInfo2FromReflection = typeof(MyClass).GetProperty("EmployeeNumber");
var e1 = MyClass.Relationships[0].Item1;
foreach (var relationship in MyClass.Relationships)
{
var body1 = (UnaryExpression)relationship.Item1.Body;
var operand1 = (MemberExpression)body1.Operand;
var propertyInfo1FromExpression = operand1.Member;
var body2 = (UnaryExpression)relationship.Item2.Body;
var operand2 = (MemberExpression)body2.Operand;
var propertyInfo2FromExpression = operand2.Member;
Console.WriteLine(propertyInfo1FromExpression.Name);
Console.WriteLine(propertyInfo2FromExpression.Name);
Console.WriteLine(propertyInfo1FromExpression == propertyInfo1FromReflection);
Console.WriteLine(propertyInfo2FromExpression == propertyInfo2FromReflection);
}
}
}
El código para propertyInfo1FromExpression
y propertyInfo2FromExpression
aquí lo resolví con un uso juicioso de la ventana Watch durante la depuración; por lo general, así descubro qué contiene un árbol de Expression
.
Ejecutando esto producirá
EmployeeId
EmployeeNumber
True
True
mostrando que podemos extraer con éxito los detalles de las propiedades relacionadas, y (crucialmente) son idénticas a las PropertyInfo
obtenidas por otros medios . Es de esperar que pueda usar esto junto con cualquier enfoque que esté utilizando para especificar propiedades de interés en tiempo de ejecución.
Si está usando C # 6.0, puede usar el nameof
Se usa para obtener el nombre de cadena simple (no calificado) de una variable, tipo o miembro. Al informar errores en el código, conectar enlaces modelo-vista-controlador (MVC), activar eventos cambiados de propiedad, etc., a menudo desea capturar el nombre de cadena de un método. Usar nameof ayuda a mantener su código válido al cambiar el nombre de las definiciones. Antes tenía que usar literales de cadena para referirse a las definiciones, lo cual es frágil al cambiar el nombre de los elementos de código porque las herramientas no saben cómo verificar estos literales de cadena.
con él puedes usar tu atributo así:
public class MyClass
{
public int EmployeeID { get; set; }
[RelatedProperty(nameof(EmployeeID))]
public int EmployeeNumber { get; set; }
}
Tener un atributo genérico no es posible de forma convencional. Sin embargo, C # y VB no lo admiten, pero CLR sí. Si quieres escribir un código IL, es posible.
Tomemos tu código:
[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute: Attribute
{
public string RelatedProperty { get; private set; }
public RelatedPropertyAttribute(string relatedProperty)
{
RelatedProperty = relatedProperty;
}
}
Compile el código, abra el ensamblado con ILSpy o ILDasm y luego ILDasm el contenido en un archivo de texto. La declaración de clase de atributo de atributo (IL) de usted se verá así:
.class public auto ansi beforefieldinit RelatedPropertyAttribute
extends [mscorlib]System.Attribute
En el archivo de texto, puede hacer que el atributo sea genérico. Hay varias cosas que deben cambiarse.
Esto simplemente se puede hacer cambiando el IL y el CLR no se quejará:
.class public abstract auto ansi beforefieldinit
RelatedPropertyAttribute`1<class T>
extends [mscorlib]System.Attribute
y ahora puede cambiar el tipo de propiedad relacionada de la cadena a su tipo genérico.
Por ejemplo:
.method public hidebysig specialname rtspecialname
instance void .ctor (
string relatedProperty
) cil managed
cambiarlo a:
.method public hidebysig specialname rtspecialname
instance void .ctor (
!T relatedProperty
) cil managed
Hay muchos marcos para hacer un trabajo "sucio" como ese: Mono.Cecil o CCI .
Como ya he dicho, no es una solución limpia orientada a objetos, solo quería señalar otra forma de romper el límite de C # y VB.
Hay una lectura interesante sobre este tema, échale un vistazo a este libro.
Espero eso ayude.
Una de las posibles soluciones consiste en definir la clase para cada relación de propiedad y hacer referencia a ella por
typeof () operador en el constructor de atributos.
Actualizado:
Por ejemplo:
[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute : Attribute
{
public Type RelatedProperty { get; private set; }
public RelatedPropertyAttribute(Type relatedProperty)
{
RelatedProperty = relatedProperty;
}
}
public class PropertyRelation<TOwner, TProperty>
{
private readonly Func<TOwner, TProperty> _propGetter;
public PropertyRelation(Func<TOwner, TProperty> propGetter)
{
_propGetter = propGetter;
}
public TProperty GetProperty(TOwner owner)
{
return _propGetter(owner);
}
}
public class MyClass
{
public int EmployeeId { get; set; }
[RelatedProperty(typeof(EmployeeIdRelation))]
public int EmployeeNumber { get; set; }
public class EmployeeIdRelation : PropertyRelation<MyClass, int>
{
public EmployeeIdRelation()
: base(@class => @class.EmployeeId)
{
}
}
}