最直接的方法是用 GetType().GetMember() 配合 BindingFlags 全面检索并用 is PropertyInfo 过滤,而非 GetProperty();需判空、注意大小写、区分动态类型与普通类型,并避免高频反射。

用 GetType().GetMember() 检查属性是否存在最直接
反射查属性,别一上来就用 GetProperty() —— 它只找 public 实例属性,漏掉 private、static、继承来的,也对索引器、字段无能为力。而 GetMember() 是更底层的入口,返回所有匹配名称的成员(属性、字段、方法、构造函数等),再用 is PropertyInfo 过滤,既准又全。
- 默认只查当前类型,加
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static才能覆盖常见场景 - 注意大小写敏感:.NET 默认区分大小写,
"Name"和"name"是两个东西 - 如果对象是
null,GetType()会直接抛NullReferenceException,务必先判空
if (obj != null && obj.GetType().GetMember("CreatedAt").Any(m => m is PropertyInfo))
{
// 属性存在
}
用 TryGetMember 前先确认类型是否支持动态访问
不是所有对象都能用 TryGetMember —— 它只对继承自 DynamicObject 或标记了 [Dynamic] 的类型有效(比如 ExpandoObject)。对普通 class、struct、int 等调用它,会走默认实现,永远返回 false,不报错也不提示,容易误判。
- 检查类型是否是
ExpandoObject或实现了IDynamicMetaObjectProvider - 别把
TryGetMember当通用反射方案,它和反射是两套机制,目标不同 - 在 JSON 反序列化后用
JObject(来自 Newtonsoft.Json)时,要用ContainsKey(),不是TryGetMember
性能差、缓存难,别在高频路径里反复反射查属性
每次调用 GetMember() 都触发元数据解析和绑定逻辑,比直接字段访问慢几十到上百倍。而且反射结果不能简单缓存——类型可能被热重载(如 ASP.NET Core 开发时)、泛型实例化不同会导致 Type 对象不同,缓存键设计稍有不慎就失效或内存泄漏。
- 如果只是初始化时检查一次(如配置类校验),没问题
- 如果在循环里、HTTP 请求处理中频繁判断,改用编译时已知的属性名 + 表达式树预编译成
Func<object, bool>,或提前生成委托缓存 - 第三方库如
FastMember提供TypeAccessor,内部做了缓存和优化,比裸反射快得多
属性名拼写错误、继承链断裂、编译器生成的属性都是坑
看起来简单的字符串匹配,实际埋着不少隐性雷。比如 get_Name() 编译后生成的属性叫 Name,但如果你手写代码调用 GetMember("get_Name"),它找不到;又比如基类里的 protected virtual string Id { get; },子类没重写也没新声明,用 BindingFlags.DeclaredOnly 就查不到。
- 属性有 backing field 但没 getter?
GetProperty()返回 null,但GetField()可能拿到字段(命名通常带<Name>k__BackingField) - 用
nameof(MyClass.Name)替代硬编码字符串,避免拼写错误和重构断连 - 涉及继承时,去掉
BindingFlags.DeclaredOnly,否则只看当前类定义,看不见父类成员
反射查属性这事,表面是“有没有”,背后牵扯类型系统、绑定规则、编译行为。写一次容易,写对且稳,得盯住那几个边界条件。









