40 votes

Autoriser un attribut personnalisé uniquement sur un type spécifique

Existe-t-il un moyen de forcer le compilateur à restreindre l'utilisation d'un attribut personnalisé à utiliser uniquement sur des types de propriétés spécifiques comme int, short, string (tous les types primitifs) ?
similaire à l'énumération ValidOn- AttributeTargetsde AttributeUsageAttribute.

31voto

Marc Gravell Points 482669

Non, vous ne pouvez pas, en gros. Vous pouvez le limiter à struct vs class interface, c'est à peu près tout. De plus : vous ne pouvez pas ajouter d'attributs à des types en dehors de votre code de toute façon (sauf via TypeDescriptor, ce qui n'est pas la même chose).

15voto

Sel Points 71

Vous pouvez exécuter ce test d'unité pour le vérifier.

Tout d'abord, déclarez l'attribut de validation PropertyType :

  [AttributeUsage(AttributeTargets.Class)]
    // [JetBrains.Annotations.BaseTypeRequired(typeof(Attribute))] uncomment if you use JetBrains.Annotations
    public class PropertyTypeAttribute : Attribute
    {
        public Type[] Types { get; private set; }

        public PropertyTypeAttribute(params Type[] types)
        {
            Types = types;
        }
    }

Créer un test unitaire :

 [TestClass]
    public class TestPropertyType 
    {
        public static Type GetNullableUnderlying(Type nullableType)
        {
            return Nullable.GetUnderlyingType(nullableType) ?? nullableType;
        }

        [TestMethod]
        public void Test_PropertyType()
        {
            var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes());
            var allPropertyInfos = allTypes.SelectMany(a => a.GetProperties()).ToArray();

            foreach (var propertyInfo in allPropertyInfos)
            {
                var propertyType = GetNullableUnderlying(propertyInfo.PropertyType);
                foreach (var attribute in propertyInfo.GetCustomAttributes(true))
                {
                    var attributes = attribute.GetType().GetCustomAttributes(true).OfType<PropertyTypeAttribute>();
                    foreach (var propertyTypeAttr in attributes)
                        if (!propertyTypeAttr.Types.Contains(propertyType))
                            throw new Exception(string.Format(
                                "Property '{0}.{1}' has invalid type: '{2}'. Allowed types for attribute '{3}': {4}",
                                propertyInfo.DeclaringType,
                                propertyInfo.Name,
                                propertyInfo.PropertyType,
                                attribute.GetType(),
                                string.Join(",", propertyTypeAttr.Types.Select(x => "'" + x.ToString() + "'"))));
                }
            }
        }
    }

Votre attribut, par exemple n'autorise que les types de propriétés décimales :

 [AttributeUsage(AttributeTargets.Property)]
    [PropertyType(typeof(decimal))]
    public class PriceAttribute : Attribute
    {

    }

Exemple de modèle :

public class TestModel  
{
    [Price]
    public decimal Price1 { get; set; } // ok

    [Price]
    public double Price2 { get; set; } // error
}

4voto

atornblad Points 5599

Vous pouvez écrire du code vous-même pour imposer une utilisation correcte de votre classe d'attributs, mais c'est tout ce que vous pouvez faire.

4voto

Kleky Points 41

Le code ci-dessous renvoie une erreur si l'attribut a été placé sur une propriété/champ qui n'est pas Liste de chaîne.

La ligne if (!(value is List<string> list)) peut être une fonction C#6 ou 7.

[AttributeUsage(AttributeTargets.Property |
                AttributeTargets.Field, AllowMultiple = false)]
public sealed class RequiredStringListAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        if (!(value is List<string> list))
            return new ValidationResult($"The required attrribute must be of type List<string>");

        bool valid = false;
        foreach (var item in list)
        {
            if (!string.IsNullOrWhiteSpace(item))
                valid = true;
        }

        return valid
            ? ValidationResult.Success
            : new ValidationResult($"This field is required"); ;
    }

}

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