03-C#.Net-特性-面试题

Q1:特性和注释有什么区别?特性的本质是什么?

出题意图: 考察对特性基础概念的理解,区分"会用"和"真懂"。

答:

注释在编译后不存在,只是给人看的文字说明。特性编译后以元数据形式存储在程序集中,运行时可以通过反射读取,能携带数据和逻辑,真正影响程序行为。

特性的本质是一个类,直接或间接继承自 System.Attribute。用 [] 标记在代码元素上,本质上是在调用这个类的构造函数。

// 这两行等价
[Custom(123)]
public class Student { }

// 编译器实际做的事:在元数据中记录 new CustomAttribute(123)

解答思路: 先说核心区别(编译后是否存在),再说本质(是一个类),最后点出标记语法的本质是调用构造函数。


Q2:AttributeUsage 的三个参数分别是什么作用?

出题意图: 考察自定义特性的规范写法,有没有实际写过特性。

答:

AttributeUsage 用来约束自定义特性的使用方式,三个参数:

  • AttributeTargets:指定特性能标记在哪里(类、方法、属性、字段等),建议每次自定义特性都明确指定,防止误用。可以用 | 组合多个目标。
  • AllowMultiple:同一位置是否允许重复标记,默认 false。验证框架里一个属性需要同时标 [Required][Length],就需要设为 true
  • Inherited:子类是否继承父类上的特性,默认 true
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class ValidateAttribute : Attribute { }

解答思路: 逐个说清楚,重点举 AllowMultiple 的实际使用场景,体现实战经验。


Q3:特性标记后为什么不能直接调用?怎么让特性生效?

出题意图: 考察对特性运行机制的理解,很多人只会标记特性,不知道背后原理。

答:

特性标记只是在元数据中记录了"这里有一个特性实例",并不会自动执行任何代码。要让特性生效,必须通过反射主动获取特性实例,然后调用其中的方法。

标准流程:先用 IsDefined() 判断是否存在(避免无谓开销),再用 GetCustomAttribute<T>() 获取实例,最后调用实例方法。

Type type = typeof(Student);

if (type.IsDefined(typeof(CustomAttribute), true))
{
    CustomAttribute attr = type.GetCustomAttribute<CustomAttribute>();
    attr.Do(); // 这里才真正执行特性中的逻辑
}

解答思路: 说清楚"标记 ≠ 执行",反射是让特性生效的唯一途径,顺带提性能优化(先判断再获取)。


Q4:如何用特性+反射实现枚举的描述信息获取?

出题意图: 这是特性"获取额外信息"的经典场景,考察能否把特性用在实际业务中。

答:

定义一个 RemarkAttribute 标记在枚举字段上,通过扩展方法封装反射逻辑,调用方只需 state.GetRemark()

[AttributeUsage(AttributeTargets.Field)]
public class RemarkAttribute : Attribute
{
    private readonly string _desc;
    public RemarkAttribute(string desc) => _desc = desc;
    public string GetRemark() => _desc;
}

public enum OrderStatus
{
    [Remark("待支付")] Pending = 1,
    [Remark("已完成")] Completed = 2
}

public static class EnumExtension
{
    public static string GetRemark(this Enum @enum)
    {
        FieldInfo field = @enum.GetType().GetField(@enum.ToString());
        if (field.IsDefined(typeof(RemarkAttribute), true))
            return field.GetCustomAttribute<RemarkAttribute>().GetRemark();
        return @enum.ToString();
    }
}

// 使用
Console.WriteLine(OrderStatus.Pending.GetRemark()); // 待支付

好处:新增枚举值只加一行标记,描述改了只改标记处,获取逻辑完全不动。

解答思路: 先说传统 if-else 的问题,再给出特性方案,最后说明优势。能写出扩展方法封装是加分项。


Q5:请设计一个基于特性的数据验证框架,要求支持扩展新的验证规则

出题意图: 综合考察特性+反射+面向对象设计(开闭原则),是中高级岗位的常见题,考察架构思维。

答:

核心设计:抽象基类定义统一接口,具体验证规则继承实现,验证管理器只依赖抽象基类,新增规则不改已有代码。

// 1. 抽象基类
public abstract class AbstractValidateAttribute : Attribute
{
    public abstract (bool ok, string error) Validate(object value);
}

// 2. 具体规则
[AttributeUsage(AttributeTargets.Property)]
public class RequiredAttribute : AbstractValidateAttribute
{
    private readonly string _msg;
    public RequiredAttribute(string msg) => _msg = msg;

    public override (bool ok, string error) Validate(object value)
    {
        bool ok = value != null && !string.IsNullOrWhiteSpace(value.ToString());
        return (ok, ok ? null : _msg);
    }
}

[AttributeUsage(AttributeTargets.Property)]
public class LengthAttribute : AbstractValidateAttribute
{
    private readonly int _min, _max;
    public string ErrorMsg { get; set; }
    public LengthAttribute(int min, int max) { _min = min; _max = max; }

    public override (bool ok, string error) Validate(object value)
    {
        if (value == null) return (true, null);
        int len = value.ToString().Length;
        bool ok = len >= _min && len <= _max;
        return (ok, ok ? null : ErrorMsg);
    }
}

// 3. 验证管理器
public static class Validator
{
    public static (bool ok, string error) Validate<T>(T obj) where T : class
    {
        foreach (PropertyInfo prop in typeof(T).GetProperties())
        {
            if (!prop.IsDefined(typeof(AbstractValidateAttribute), true)) continue;

            object value = prop.GetValue(obj);
            foreach (AbstractValidateAttribute attr in prop.GetCustomAttributes<AbstractValidateAttribute>())
            {
                var (ok, error) = attr.Validate(value);
                if (!ok) return (false, error);
            }
        }
        return (true, null);
    }
}

// 4. 使用
public class UserDto
{
    [Required("姓名不能为空")]
    public string Name { get; set; }

    [Required("手机号不能为空")]
    [Length(11, 11, ErrorMsg = "手机号必须11位")]
    public string Mobile { get; set; }
}

var (ok, error) = Validator.Validate(new UserDto { Name = "张三", Mobile = "123" });
// ok=false, error="手机号必须11位"

扩展新规则只需新建一个继承 AbstractValidateAttribute 的类,Validator 完全不用改。

解答思路: 先说设计思路(抽象基类+开闭原则),再写代码,最后说明扩展方式。能主动提到"不改 Validator 就能扩展"是加分项。


Q6:反射获取特性时,IsDefinedGetCustomAttributes 都能判断特性是否存在,为什么推荐先用 IsDefined

出题意图: 考察对反射性能的关注,体现候选人有没有在意代码质量。

答:

GetCustomAttributes() 会实例化所有特性对象并返回数组,即使你只是想判断"有没有",也会产生对象分配开销。IsDefined() 只做元数据检查,不创建特性实例,性能更好。

在循环中遍历大量属性/方法时,这个差异会被放大:

// 推荐:先判断,有才获取
if (prop.IsDefined(typeof(RequiredAttribute), true))
{
    var attr = prop.GetCustomAttribute<RequiredAttribute>();
    attr.Validate(value);
}

// 不推荐:每次都创建实例,即使不需要
var attrs = prop.GetCustomAttributes<RequiredAttribute>(true);
if (attrs.Any()) { ... }

解答思路: 说清楚两者的本质区别(是否创建实例),结合循环场景说明影响。


Q7:特性的 Inherited 参数设为 true 时,子类能获取到父类的特性吗?有什么注意事项?

出题意图: 考察对特性继承机制的细节掌握,容易踩坑的地方。

答:

Inherited = true 时,通过 GetCustomAttributes(true) 可以获取到继承链上的特性。但有一个重要细节:接口上的特性不会被实现类继承,只有类上的特性才会被子类继承。

[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public class TagAttribute : Attribute { }

[Tag]
public class Base { }

public class Child : Base { }

// 可以获取到,因为 Child 继承自 Base
bool has = typeof(Child).IsDefined(typeof(TagAttribute), true); // true

// 接口上的特性不会被实现类继承
[Tag]
public interface IService { }

public class MyService : IService { }
bool hasFromInterface = typeof(MyService).IsDefined(typeof(TagAttribute), true); // false

另外,GetCustomAttributes(false) 只获取当前类型自身的特性,不搜索继承链。

解答思路: 先回答"可以",再说接口特性不继承这个坑,最后说 false/true 参数的区别。

posted @ 2026-03-19 14:07  龙猫•ᴥ•  阅读(1)  评论(0)    收藏  举报