只允许在特定类型上使用自定义属性

本文关键字:自定义属性 类型 | 更新日期: 2023-09-27 18:16:36

是否有一种方法可以强制编译器将自定义属性的使用限制为仅用于特定的属性类型,如int, short, string(所有基本类型)?
类似于AttributeUsageAttribute的ValidOn-AttributeTargets枚举。

只允许在特定类型上使用自定义属性

基本上不能。你可以把它限制在struct vs class vs interface,就是这样。另外:你不能添加属性到代码之外的类型(除了通过TypeDescriptor,这是不一样的)。

您可以运行这个单元测试来检查它。

首先,声明验证属性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;
        }
    }
创建单元测试:
 [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() + "'"))));
                }
            }
        }
    }

您的属性,例如,只允许十进制属性类型:

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

示例模型:

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

如果将属性放置在非List of string的属性/字段上,下面的代码将返回一个错误。

if (!(value is List<string> list))行可能是c# 6或c# 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"); ;
    }
}

您可以自己编写代码来强制正确使用您的属性类,但这是您所能做的。

我是这样做的:

[AttributeUsage(AttributeTargets.Property)]
public class SomeValidationAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value is not string stringToValidate)
        {
            throw new AttributeValueIsNotStringException(validationContext.DisplayName, validationContext.ObjectType.Name);
        }
        // validationContext.DisplayName is name of property, where validation attribut was used.
        // validationContext.ObjectType.Name is name of class, in which the property is placed to instantly identify, where is the error.
        
        //Some validation here.
        return ValidationResult.Success;
    }
}

和例外看起来像这样:

public class AttributeValueIsNotStringException : Exception
{
    public AttributeValueIsNotStringException(string propertyName, string className) : base(CreateMessage(propertyName, className))
    {
    }
    private static string CreateMessage(string propertyName, string className)
    {
        return $"Validation attribute cannot be used for property: '"{propertyName}'" in class: '"{className}'" because it's type is not string. Use it only for string properties.";
    }
}