C# 高级教程 - 02 反射与特性

反射(Reflection)是 .NET 运行时提供的元数据内省能力。通过反射,可以在运行时获取类型信息、调用成员、创建实例,甚至动态生成代码。特性(Attribute)则是向程序实体附加元数据的声明式工具。两者密切配合,是实现序列化、依赖注入、ORM、AOP 等高级框架的技术基石。

反射核心 API

1
2
3
4
5
6
7
8
9
10
11
12
using System.Reflection;

// 获取类型信息的三种方式
Type type1 = typeof(string);
Type type2 = "hello".GetType();
Type type3 = Type.GetType("System.String");

// 遍历成员
foreach (MethodInfo method in type1.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}");
}

动态创建类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface IPlugin
{
string Execute(string input);
}

public class UppercasePlugin : IPlugin
{
public string Execute(string input) => input.ToUpper();
}

// 运行时按类型名动态加载
Type pluginType = Assembly.Load("MyApp").GetType("MyApp.UppercasePlugin");
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType)!;
Console.WriteLine(plugin.Execute("hello")); // HELLO

运行时调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Calculator
{
public int Add(int a, int b) => a + b;
private int Multiply(int a, int b) => a * b;
}

Type calcType = typeof(Calculator);
object calc = Activator.CreateInstance(calcType)!;

// 调用公开方法
MethodInfo addMethod = calcType.GetMethod("Add", new[] { typeof(int), typeof(int) })!;
int result = (int)addMethod.Invoke(calc, new object[] { 3, 4 })!; // 7

// 调用私有方法(破坏封装,谨慎使用)
MethodInfo mulMethod = calcType.GetMethod("Multiply",
BindingFlags.NonPublic | BindingFlags.Instance)!;
int secret = (int)mulMethod.Invoke(calc, new object[] { 3, 4 })!; // 12

自定义特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class AuthorAttribute : Attribute
{
public string Name { get; }
public int Priority { get; set; }

public AuthorAttribute(string name) => Name = name;
}

[Author("张三", Priority = 1)]
[Author("李四", Priority = 2)]
public class SampleService
{
[Author("王五")]
public void DoWork() { }
}

运行时读取特性

1
2
3
4
5
6
7
8
public static void PrintAuthorInfo(Type type)
{
var authors = type.GetCustomAttributes<AuthorAttribute>();
foreach (var author in authors)
{
Console.WriteLine($"{type.Name}: {author.Name} (优先级: {author.Priority})");
}
}

条件特性(Conditional)

1
2
3
4
5
6
7
8
9
10
11
#define DEBUG
using System.Diagnostics;

public class Logger
{
[Conditional("DEBUG")]
public static void Log(string message)
{
Console.WriteLine($"[DEBUG] {message}");
}
}

DEBUG 符号未定义时,所有对 Log 的调用在编译时被移除——这与普通条件语句不同,调用方甚至连方法调用的 IL 都不会生成。

反射与泛型

1
2
3
4
5
6
7
8
9
10
11
12
class GenericDemo<T>
{
public T? Value { get; set; }
}

Type genericType = typeof(GenericDemo<>);
Type closedType = genericType.MakeGenericType(typeof(int));
object instance = Activator.CreateInstance(closedType)!;

var prop = closedType.GetProperty("Value");
prop!.SetValue(instance, 42);
Console.WriteLine(prop.GetValue(instance)); // 42

特性在框架中的应用

1. 验证框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[AttributeUsage(AttributeTargets.Property)]
public class RequiredAttribute : Attribute { }

[AttributeUsage(AttributeTargets.Property)]
public class RangeAttribute : Attribute
{
public int Min { get; }
public int Max { get; }
public RangeAttribute(int min, int max) => (Min, Max) = (min, max);
}

public class UserModel
{
[Required]
public string Name { get; set; } = "";

[Range(1, 120)]
public int Age { get; set; }
}

// 通用验证器
public static List<string> Validate<T>(T obj)
{
var errors = new List<string>();
foreach (var prop in typeof(T).GetProperties())
{
var value = prop.GetValue(obj);
if (prop.GetCustomAttribute<RequiredAttribute>() != null &&
(value == null || string.IsNullOrWhiteSpace(value?.ToString())))
{
errors.Add($"{prop.Name} 是必填项");
}

var range = prop.GetCustomAttribute<RangeAttribute>();
if (range != null && value is int num && (num < range.Min || num > range.Max))
{
errors.Add($"{prop.Name} 必须在 {range.Min}-{range.Max} 之间");
}
}
return errors;
}

2. 配置文件映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[AttributeUsage(AttributeTargets.Property)]
public class ConfigKeyAttribute : Attribute
{
public string Key { get; }
public ConfigKeyAttribute(string key) => Key = key;
}

public static T MapFromConfig<T>(IConfiguration config) where T : new()
{
var obj = new T();
foreach (var prop in typeof(T).GetProperties())
{
var attr = prop.GetCustomAttribute<ConfigKeyAttribute>();
if (attr != null && config[attr.Key] is string value)
{
prop.SetValue(obj, Convert.ChangeType(value, prop.PropertyType));
}
}
return obj;
}

总结:反射赋予程序内省自身结构的能力,特性则为元数据提供声明式表达。两者结合,催生了 .NET 生态中大量的现代化框架。反射虽然有性能开销(方法调用比直接调用慢 10-100 倍),但在启动时结合缓存、emit、Source Generator 等技术可以极大缓解。在需要元编程的地方,反射是 C# 开发者手中不可或缺的利器。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: