53 votes

Expression lambda dans le constructeur d'attribut

J'ai créé un Attribute catégorie RelatedPropertyAttribute:

[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute: Attribute
{
    public string RelatedProperty { get; private set; }

    public RelatedPropertyAttribute(string relatedProperty)
    {
        RelatedProperty = relatedProperty;
    }
}

Je l'utilise pour indiquer des propriétés connexes dans une classe. Exemple de la façon dont je voudrais l'utiliser:

public class MyClass
{
    public int EmployeeID { get; set; }

    [RelatedProperty("EmployeeID")]
    public int EmployeeNumber { get; set; }
}

Je voudrais utiliser des expressions lambda afin que je puisse passer d'un type fort dans mon attribut du constructeur, et non pas un "la magie de la chaîne". De cette façon, je peux exploiter compilateur vérification de type. Par exemple:

public class MyClass
{
    public int EmployeeID { get; set; }

    [RelatedProperty(x => x.EmployeeID)]
    public int EmployeeNumber { get; set; }
}

J'ai pensé que je pouvais le faire avec la suite, mais il n'est pas permis par le compilateur:

public RelatedPropertyAttribute<TProperty>(Expression<Func<MyClass, TProperty>> propertyExpression)
{ ... }

Erreur:

Le non-générique de type "RelatedPropertyAttribute' ne peut pas être utilisé avec les arguments de type

Comment puis-je y parvenir?

57voto

JohnField Points 683

Avoir un générique attribut n'est pas possible de façon conventionnelle. Cependant, C# et VB n'est pas prise en charge, mais le CLR ne. Si vous voulez écrire un peu de code IL il est possible.

Prenons votre code:

[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute: Attribute
{
    public string RelatedProperty { get; private set; }

    public RelatedPropertyAttribute(string relatedProperty)
    {
       RelatedProperty = relatedProperty;
    }
}

Compiler le code, de s'ouvrir à l'assemblée ILSpy ou ILDasm puis vider le contenu d'un fichier texte. le IL de vous attribuer la déclaration de la classe va donner ceci:

.class public auto ansi beforefieldinit RelatedPropertyAttribute
extends [mscorlib]System.Attribute

Dans le fichier texte, vous pouvez alors faire de l'attribut générique. Il y a plusieurs choses qui doivent être changées

Cela peut être simplement changé à cela et le CLR ne se plaignent:

.class public abstract auto ansi beforefieldinit
      RelatedPropertyAttribute`1<class T>
      extends [mscorlib]System.Attribute

et maintenant, vous pouvez modifier le type de relatedProperty de chaîne à votre type générique.

Par Exemple:

.method public hidebysig specialname rtspecialname 
    instance void .ctor (
        string relatedProperty
    ) cil managed

changé:

.method public hidebysig specialname rtspecialname 
    instance void .ctor (
        !T relatedProperty
    ) cil managed

Il y a beaucoup de cadres pour ce faire "sale" boulot comme Mono.Cecil ou de la CCI.

Comme je l'ai déjà dit, il n'est pas propre OOP solution mais je voulais juste souligner une autre façon de briser la limite de C# et VB.

Il y a une intéressante lecture autour de ce thème, check it out ce livre.

J'espère que ça aide

44voto

Marc Gravell Points 482669

Vous ne pouvez pas

  • vous ne pouvez pas créer de types d'attributs génériques (ce n'est tout simplement pas autorisé); de même, aucune syntaxe d' utilisation des attributs génériques ( [Foo<SomeType>] ) n'est définie
  • vous ne pouvez pas utiliser lambdas dans les initialiseurs d'attribut - les valeurs disponibles pour passer aux attributs sont très limitées et n'incluent tout simplement pas les expressions (qui sont très complexes et sont des objets d'exécution, pas des littéraux au moment de la compilation)

9voto

GennadyGS Points 31

L'une des solutions de contournement possibles consiste à défiler la classe pour chaque relation de propriété et à la référencer par
Opérateur typeof () dans le constructeur d'attribut.

Mis à jour:

Par exemple:

 [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(EmployeeNumberRelation))]
    public int EmployeeNumber { get; set; }

    public class EmployeeNumberRelation : PropertyRelation<MyClass, int>
    {
        public EmployeeNumberRelation()
            : base(@class => @class.EmployeeNumber)
        {

        }
    }

}
 

5voto

FDL Points 931

Vous ne pouvez pas. Les types des attributs sont limitées comme écrit ici. Ma suggestion, essayez d'évaluer votre expression lambda à l'extérieur, puis utilisez l'un des types suivants:

  • Les types simples (bool, byte, char, short, int, long, float et double)
  • chaîne
  • Système.Type
  • les enums
  • objet (L'argument d'un attribut de paramètre du type d'objet doit être une valeur constante de l'un des types ci-dessus.)
  • Des tableaux unidimensionnels de l'un des types ci-dessus

4voto

AakashM Points 32891

Pour développer mon commentaire, c'est une façon de réaliser votre tâche avec une approche différente. Vous dites que vous voulez "indiquent des propriétés connexes dans une classe", et que "vous souhaitez utiliser des expressions lambda afin que je puisse passer d'un type fort dans mon attribut du constructeur, et non pas un "la magie de la chaîne". De cette façon, je peux exploiter compilateur le type de la vérification".

Ici est donc un moyen d'indiquer des propriétés liées à l'est au moment de la compilation tapé et n'a pas toute la magie des cordes:

public class MyClass
{
    public int EmployeeId { get; set; }
    public int EmployeeNumber { get; set; }
}

C'est la classe considérée. Nous voulons indiquer que EmployeeId et EmployeeNumber sont liées. Pour un peu de code de la concision, nous allons mettre en œuvre ce type d'alias jusqu'en haut du fichier de code. Il n'est pas nécessaire du tout, mais il ne rendre le code moins intimidant:

using MyClassPropertyTuple = 
    System.Tuple<
            System.Linq.Expressions.Expression<System.Func<MyClass, object>>,
            System.Linq.Expressions.Expression<System.Func<MyClass, object>>
        >;

Cela rend MyClassPropertyTuple d'un alias pour un Tuple deux Expressions, dont chacun saisit la définition d'une fonction à partir d'un MyClass à un objet. Par exemple, la propriété des getters sur MyClass sont de telles fonctions.

Maintenant, nous allons saisir la relation. Ici, j'ai fait une statique sur propery MyClass, mais cette liste pourrait être définie n'importe où:

public class MyClass
{
    public static List<MyClassPropertyTuple> Relationships
        = new List<MyClassPropertyTuple>
            {
                new MyClassPropertyTuple(c => c.EmployeeId, c => c.EmployeeNumber)
            };
}

Le compilateur C# sait que nous sommes la construction d'un Tuple de Expressions, de sorte que nous n'avons pas besoin des conversions explicites en face de ces expressions lambda - ils sont automatiquement mises en Expressions.

C'est essentiellement en termes de définition, ceux - EmployeeId et EmployeeNumber mentions sont fortement typés et appliquées au moment de la compilation, et le refactoring qui n'propriété renomme devrait être en mesure de trouver ces usages lors d'un changement de nom (ReSharper peut certainement). Il n'y a pas la magie des cordes ici.


Mais bien sûr, nous voulons aussi être en mesure d'interroger les relations au moment de l'exécution (j'assume!). Je ne sais pas exactement comment vous souhaitez le faire si ce code est juste à titre indicatif.

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

Le code pour propertyInfo1FromExpression et propertyInfo2FromExpression ici j'ai travaillé avec l'utilisation judicieuse de la fenêtre d'observation pendant le débogage, ce qui est habituellement la façon dont je travaille ce qu'un Expression arbre contient en fait.

L'exécution de cette volonté de produire

EmployeeId
EmployeeNumber
True
True

montrer que l'on peut extraire correctement les détails des propriétés, et (cruciale) ils sont de référence identique à l' PropertyInfos obtenus par d'autres moyens. Nous espérons que vous pouvez utiliser ceci en conjonction avec quelle que soit l'approche que vous êtes en train d'utiliser pour spécifier les propriétés d'intérêt au moment de l'exécution.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X