C# 语言进阶(七)属性、索引器、反射

本篇 核心知识点 :属性 Property、索引器 Indexer、反射 Reflection、类型判断 is/as、字符串安全转换 TryParse、反射实战(创建对象 / 调用方法 / 读取特性)、反射优缺点与项目使用规范

一、属性 Property

1. 概念

属性是类 / 结构体 / 接口的访问器,封装私有字段,对外提供统一读写入口,替代传统GetXxx()/SetXxx()函数,读写语法如同普通字段,但内部可附加校验、事件同步等业务逻辑。

2. 核心特性

  1. 拆分get读取访问器、set写入访问器;

  2. set内置隐式关键字value,代表外部赋值传入的值;

  3. 可做读写控制:仅写 get = 只读,仅写 set = 只写;

  4. 赋值、取值时自动触发内部代码(血量边界校验、UI 刷新、事件广播);

  5. 分为完整属性(配套私有字段)、自动实现属性(编译器自动生成隐藏字段)。

3. 完整手写属性(配套私有字段)

复制代码
class Npc
{
    // 私有字段,外部禁止直接访问
    private int _hp;
    private string _name;
​
    // 血量属性:带边界校验
    public int Hp
    {
        get { return _hp; }
        set
        {
            // 血量约束 0 ~ 2000
            if (value < 0) _hp = 0;
            else if (value > 2000) _hp = 2000;
            else _hp = value;
            // 赋值后自动同步UI
            Console.WriteLine("血量更新,刷新血条");
        }
    }
​
    // 只读属性:只提供get,外部无法赋值
    public string Name
    {
        get { return _name; }
    }
​
    // 构造初始化
    public Npc(string name, int hp)
    {
        _name = name;
        Hp = hp; // 走set自动校验
    }
}
​
static void Main()
{
    Np n = new N("僵尸", 3000);
    Console.WriteLine(n.Hp); // 读取get,输出2000
    n.Hp = -50;
    Console.WriteLine(n.Hp); // 输出0
}

4. 自动实现属性(简化写法)

无需手动定义私有字段,编译器自动生成隐藏后备字段,适合无额外逻辑场景:

复制代码
// 读写自动属性
public int Mp { get; set; }
// 只读自动属性(构造内赋值)
public int MaxAtk { get; }

5. 单例模式只读属性实战(饿汉)

复制代码
class ResManager
{
    // 私有静态实例
    private static ResManager _inst = new ResManager();
    // 只读对外属性,无set,外部不能修改
    public static ResManager Instance
    {
        get { return _inst; }
    }
    // 私有构造,禁止外部new
    private ResManager() { }
}

6. 抽象属性(抽象类专用)

抽象类中无实现的属性,派生类必须完整重写 get/set:

复制代码
abstract class Person
{
    public abstract string Name { get; set; }
}
class Player : Person
{
    private string _n;
    public override string Name
    {
        get { return _n; }
        set { _n = value; }
    }
}

拓展工程价值

游戏开发中金币、血量用属性赋值,可直接在 set 内写 UI 刷新、事件广播(观察者模式),无需每次修改后手动调用刷新函数。

二、索引器 Indexer

1. 概念

特殊的类访问器,使用this[]定义,让类对象可以像数组下标一样访问内部多组私有数据,本质是重载下标访问运算符。

2. 核心规则

  1. 固定语法public 类型 this[索引参数] {get;set;}

  2. 索引参数支持 int/string 等任意类型;

  3. 一个类可重载多个索引器(参数类型 / 个数不同);

  4. 内部通过分支区分不同下标对应字段 / 数组。

3. 基础 int 索引器代码示例

复制代码
class Npc
{
    private int _hp = 1000;
    private int _mp = 500;
    // int下标索引器 0=血量 1=蓝量
    public int this[int idx]
    {
        get
        {
            switch (idx)
            {
                case 0: return _hp;
                case 1: return _mp;
                default: return 0;
            }
        }
        set
        {
            switch (idx)
            {
                case 0: _hp = value; break;
                case 1: _mp = value; break;
            }
        }
    }
}
static void Main()
{
    Npc n = new Npc();
    n[0] = 1500; // 修改hp
    Console.WriteLine(n[1]); // 读取mp
}

4. 重载:string 字符串索引器(按名称取值)

复制代码
// 重载字符串索引,通过字段名匹配
public int this(string key)
{
    get
    {
        if (key == "hp") return _hp;
        if (key == "mp") return _mp;
        return -1;
    }
}

5. 拓展实战场景

读取配置表、消息数据包时,循环通过索引批量赋值内部字段,简化大量重复赋值代码。

三、反射 Reflection

1. 核心概念

反射是 C# 运行时操作元数据的机制:程序运行期间,通过字符串类名 / 方法名,动态获取类型、构造、方法、属性,创建对象、调用函数、读取特性

2. 反射核心元数据类(System.Reflection 命名空间)

作用
Type 存储类型全部元数据(类 / 结构体 / 接口)
ConstructorInfo 构造函数信息
MethodInfo 普通方法信息
PropertyInfo 属性信息
FieldInfo 私有字段信息
Attribute 特性元数据读取载体

3. 获取 Type 三种方式

复制代码
class Plant
{
    public string Name { get; set; }
    public int Hp;
    public void ShowInfo() => Console.WriteLine(Name + " " + Hp);
}
​
static void Main()
{
    // 方式1 类型直接获取
    Type t1 = typeof(Plant);
    // 方式2 通过实例对象获取
    Plant p = new Plant();
    Type t2 = p.GetType();
    // 方式3 完整字符串类名(含命名空间)
    Type t3 = Type.GetType("命名空间.Plant");
}

4. 反射创建对象(调用构造函数)

复制代码
Type t = typeof(Plant);
// 1. 获取构造函数:参数类型数组
ConstructorInfo ctor = t.GetConstructor(new[] { typeof(string), typeof(int) });
// 2. 传入参数数组,实例化对象(object基类接收)
object obj = ctor.Invoke(new object[] { "向日葵", 100 });
// 3. 类型转换
Plant p = obj as Plant;

5. 反射调用类方法

复制代码
// 获取ShowInfo方法元数据
MethodInfo method = t.GetMethod("ShowInfo");
// 调用,第一个参数为实例,无参数传null
method.Invoke(obj, null);

6. 反射读取 / 修改属性

复制代码
PropertyInfo prop = t.GetProperty("Name");
// 读取
string name = prop.GetValue(obj).ToString();
// 修改
prop.SetValue(obj, "豌豆射手");

7. 反射读取自定义特性

依托前期 Attribute 特性,运行时通过 Type 获取标记的元数据备注、版本信息。

8. 反射优缺点

优点
  1. 完全动态:仅字符串即可创建对象、调用方法;

  2. 游戏热更新、插件系统、配置表序列化底层核心;

缺点
  1. 性能极差,元数据查找、Invoke 调用开销远高于直接代码;

  2. 破坏封装,可读取私有字段 / 方法;

9 项目使用规范

仅在初始化、资源加载一次性使用;禁止 Update / 循环内频繁反射调用,避免帧率暴跌。

四、类型安全判断与转换 is /as

1. is 关键字

概念:

判断对象是否属于目标类型,返回 bool,不会转换,无异常。

复制代码
object obj = new Plant("坚果", 2000);
if(obj is Plant)
{
    Console.WriteLine("该对象是植物类型");
}

2. as 安全转换

概念:

强制类型转换升级版,转换失败返回 null,不抛异常;仅引用类型可用。

复制代码
object obj = new Plant("坚果", 2000);
Plant p = obj as Plant;
if(p != null) p.ShowInfo();

对比:直接强转(Plant)obj转换失败直接程序崩溃,生产优先as

五、字符串安全转换 TryParse

1. 传统 Parse 缺陷

int.Parse("abc")格式错误直接抛异常,程序崩溃;

2. TryParse 安全转换(out 输出参数)

概念:

转换成功返回 true,失败返回 false,转换结果存入 out 变量,无崩溃风险。

复制代码
string input = "123";
int num;
// out 变量无需提前初始化
bool succ = int.TryParse(input, out num);
if(succ) Console.WriteLine(num);
else Console.WriteLine("数字格式错误");

拓展

所有基础数值类型均提供TryParse:double、float、bool 等。

六、out 参数补充(反射 / 转换高频)

规则

  1. 方法内必须对 out 参数赋值,编译强制校验;

  2. 调用方变量无需初始化;

  3. 多用于函数多输出(转换结果、错误码)。

七、拓展

  1. 属性与普通 Get/Set 函数区别、游戏 UI 刷新实战用法;

  2. 索引器适用场景、支持多类型重载;

  3. 反射核心使用流程、性能缺陷与项目限制;

  4. is判断、as安全转换与直接强转三者差异;

  5. TryParse相比 Parse 的优势;

  6. 自动属性与完整带字段属性区分;

  7. 反射游戏热更新、插件系统底层原理。