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

问题描述 投票:0回答:6

有没有办法强制编译器限制自定义属性的使用,使其仅用于特定的 property 类型,如 int、short、string(所有原始类型)?
类似于 AttributeUsageAttribute 的 ValidOn-AttributeTargets 枚举。

c# .net compiler-construction attributes
6个回答
35
投票

不,基本上你不能。您可以将其限制为

struct
vs
class
vs
interface
,仅此而已。另外:无论如何,您都无法向代码外部的类型添加属性(除了通过
TypeDescriptor
,这是不一样的)。


20
投票

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

首先声明验证属性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
}

5
投票

如果属性放置在不是字符串列表的属性/字段上,下面的代码将返回错误。

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

}

4
投票

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


0
投票

我这样做的方式如下:

[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.";
    }
}

0
投票

对于原始类型和密封类型,你很不幸。

但是,您确实可以通过将属性声明为具有受保护或内部可见性的内部类,将属性的使用(和可见性)限制为类型层次结构。

当然,这里存在各种限制和极端情况,但是这是可能的,并且可能适用于各种用例。

内部可见性可以允许您从其他程序集访问这些字段(如果您使这些字段对这些程序集可见)。

这里是一个简单的自动验证时属性绑定属性的示例。在 Unity 2023.1.12f1 中测试。

using System;
using System.Reflection;
using UnityEngine;

namespace Tiger.Attributes
{
    public class AutoBehaviour : MonoBehaviour
    {
        [AttributeUsage(AttributeTargets.Field)]
        protected class AutoAttribute : PropertyAttribute { }
        
        private void OnValidate()
        {
            var object_fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (var field_info in object_fields)
            {
                if (Attribute.GetCustomAttribute(field_info, typeof(AutoAttribute)) is not AutoAttribute) continue;

                var value = GetComponent(field_info.FieldType);
                field_info.SetValue(this, value);
            }
        }
    }
}

用法很简单,例如,如果您将此脚本组件附加到 Cube 原始游戏对象,它将自动查找并序列化适当的组件。

using Tiger.Attributes;
using UnityEngine;

namespace Jovian
{
    public class TestBehaviour : AutoBehaviour
    {
        [Auto] public MeshFilter meshFilter;
        [Auto] public MeshRenderer meshRenderer;
        
        // Start is called before the first frame update
        private void Start()
        {
        
        }

        // Update is called once per frame
        private void Update()
        {
        
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.