惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 世纪末の魔术师

坚毅,是一种缓慢修理自己的方式 语言的边界,与软件的命运 人类与AI协同进化 《坚毅》第一部分读书笔记 用 System.CommandLine 构建工程级 CLI 工具 用 Command 模式构建可扩展的命令行工具 从哎呦”到语言宇宙 ——读《What Is ChatGPT Doing … And Why Does It Work?》 ⏱️ 深入理解定时器中的【时间轮算法】 🚫 为什么「定时器」不应该是线程安全的? C# AOT编译后——调用其类库方法因顺序出错? UnitTask中的Forget()与 CTS 光线追踪和球体追踪 八、方法(method) 二十、异常与状态管理(Exception&State Management) 二十八、IO绑定的异步操作(IO-Bound Async) 二十二、CLR寄宿与AppDomain(CLR Hosting and App Domains ) 二十九、原始线程同步构造(Primitive Thread Synchronization Constructs ) 二十六、线程与并发(Thread Basic) 二十七、计算密集型异步操作(Compute-Bound Asynchronous Operations)
二十三、程序集加载与反射(Assembly Loading and Reflection)
世纪末の魔术师 · 2025-08-26 · via 博客园 - 世纪末の魔术师

CLR #反射 #reflect

程序集加载与反射(Assembly Loading and Reflection)

反射(Reflection)是.NET框架中一个强大的功能,允许程序在运行时动态地检查和操作类型、方法、属性等元数据。

1. 程序集加载(Assembly Loading)

在.NET中,程序集(Assembly)是代码和元数据的容器。程序集加载是反射的基础,CLR(公共语言运行时)通过Assembly类提供多种加载方式:

  • Assembly.Load:根据程序集的强名称(包括名称、版本、文化和公钥标记)加载程序集。适用于需要明确版本控制的场景。
  • Assembly.LoadFrom:通过文件路径或URL加载程序集。如果程序集已加载,则返回已加载的实例。
  • ReflectionOnlyLoad/ReflectionOnlyLoadFrom:加载程序集仅用于元数据检查,禁止执行代码,适合分析工具或处理不受信任的程序集。

以下是一个简单的示例,展示如何使用Assembly.Load加载程序集并获取其类型:

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        // 加载程序集
        Assembly assembly = Assembly.Load("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
        
        // 获取程序集中的所有公共类型
        Type[] types = assembly.GetExportedTypes();
        foreach (Type type in types)
        {
            Console.WriteLine($"Type: {type.FullName}");
        }
    }
}

注意:使用ReflectionOnlyLoad时,需要注册回调方法以加载引用的程序集,因为CLR不会自动加载它们。

Mermaid图表:程序集加载流程

graph TD A[程序启动] --> B[调用Assembly.Load] B --> C[CLR查找TypeRef和AssemblyRef] C --> D{程序集是否已加载?} D -->|是| E[返回已加载的Assembly对象] D -->|否| F[加载程序集到AppDomain] F --> G[解析元数据] G --> H[返回Assembly对象]

2. 发现类型(Discovering Types)

反射允许开发者在运行时发现程序集中定义的类型。常用的方法包括:

  • Type.GetType:根据类型名称获取Type对象。
  • Assembly.GetExportedTypes:获取程序集中的所有公共类型。
  • TypeInfo.DeclaredMembers:获取类型的成员(字段、方法、属性等)。

以下代码展示如何遍历程序集中的类型并列出其成员:

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        // 加载当前程序集
        Assembly assembly = Assembly.GetExecutingAssembly();
        
        // 获取所有类型
        foreach (Type type in assembly.GetTypes())
        {
            Console.WriteLine($"Type: {type.Name}");
            foreach (MemberInfo member in type.GetTypeInfo().DeclaredMembers)
            {
                Console.WriteLine($"  Member: {member.Name} ({member.MemberType})");
            }
        }
    }
}

关键点:为了提高性能,建议使用接口或基类进行早期绑定,而不是频繁使用反射来访问成员。

3. 调用类型成员(Invoking Members)

反射不仅可以发现类型,还可以动态调用其成员(如方法、属性、构造函数)。以下是调用成员的主要方式:

  • MethodInfo.Invoke:调用方法。
  • PropertyInfo.GetValue/SetValue:读取或设置属性值。
  • ConstructorInfo.Invoke:调用构造函数。

示例代码展示如何动态调用方法和设置属性:

using System;
using System.Reflection;

class TestClass
{
    public string Name { get; set; }
    public void SayHello(string message) => Console.WriteLine($"Hello, {message}!");
}

class Program
{
    static void Main()
    {
        // 获取类型
        Type type = typeof(TestClass);
        
        // 创建实例
        object instance = Activator.CreateInstance(type);
        
        // 设置属性
        PropertyInfo prop = type.GetProperty("Name");
        prop.SetValue(instance, "Grok");
        
        // 调用方法
        MethodInfo method = type.GetMethod("SayHello");
        method.Invoke(instance, new object[] { "World" });
    }
}

输出

Hello, World!

性能优化:反射调用会带来性能开销,因为它涉及字符串搜索和参数打包。为提高性能,可以:

  1. 使用Delegate.CreateDelegate创建委托,缓存方法调用。
  2. 使用运行时句柄(如RuntimeMethodHandle)减少内存占用。

以下是使用委托优化方法调用的示例:

using System;
using System.Reflection;

class TestClass
{
    public void SayHello(string message) => Console.WriteLine($"Hello, {message}!");
}

class Program
{
    static void Main()
    {
        Type type = typeof(TestClass);
        object instance = Activator.CreateInstance(type);
        
        MethodInfo method = type.GetMethod("SayHello");
        var sayHelloDelegate = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), instance, method);
        
        sayHelloDelegate("World"); // 更高效的调用
    }
}

4. 设计支持插件的应用程序

反射在构建可扩展的应用程序(如插件系统)时非常有用。以下是设计插件系统的关键步骤:

  1. 定义一个独立的HostSDK程序集,包含接口或基类。
  2. 插件开发者引用HostSDK,实现接口。
  3. 主机应用程序动态加载插件程序集并调用其类型。

以下是一个简单的插件系统示例:

using System;
using System.IO;
using System.Reflection;

public interface IPlugin
{
    void Execute();
}

class Program
{
    static void Main()
    {
        // 加载所有.dll文件
        foreach (string file in Directory.GetFiles(".", "*.dll"))
        {
            Assembly assembly = Assembly.LoadFrom(file);
            foreach (Type type in assembly.GetTypes())
            {
                if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface)
                {
                    IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
                    plugin.Execute();
                }
            }
        }
    }
}

建议:将接口定义在单独的程序集中,避免版本控制问题。考虑使用MEF(Managed Extensibility Framework)简化插件注册和发现。

5. 反射的性能注意事项

反射虽然强大,但性能开销较大,主要原因包括:

  • 字符串搜索:元数据扫描涉及大量的字符串比较。
  • 参数打包:调用方法时需要将参数打包为数组。
  • 类型检查:CLR需要验证参数类型和访问权限。

优化策略:

  • 使用接口或基类进行早期绑定。
  • 缓存TypeMethodInfo等对象。
  • 使用运行时句柄(如RuntimeTypeHandle)减少内存占用。

以下是使用运行时句柄的示例:

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        Type type = typeof(string);
        RuntimeTypeHandle handle = Type.GetTypeHandle(type);
        
        // 从句柄恢复Type对象
        Type restoredType = Type.GetTypeFromHandle(handle);
        Console.WriteLine($"Restored Type: {restoredType.FullName}");
    }
}

6. 常见面试题及解析

以下是一些与反射和程序集加载相关的常见面试题:

Q1:什么是反射?在C#中有哪些实际应用场景?

解析:反射是运行时检查和操作类型元数据的机制。常见应用包括:

  • 动态加载插件或模块。
  • 序列化/反序列化对象(如JSON库)。
  • 开发工具(如Visual Studio的属性窗口)。
  • 单元测试框架(如NUnit)动态调用测试方法。

Q2:如何优化反射的性能?

解析:反射性能开销较大,可通过以下方式优化:

  • 使用接口或基类进行早期绑定。
  • 缓存TypeMemberInfo对象。
  • 使用Delegate.CreateDelegate创建委托。
  • 使用运行时句柄(如RuntimeMethodHandle)减少内存占用。

Q3:Assembly.Load和Assembly.LoadFrom有什么区别?

解析

  • Assembly.Load:根据强名称加载程序集,优先从GAC(全局程序集缓存)查找,适合需要严格版本控制的场景。
  • Assembly.LoadFrom:根据文件路径加载程序集,适合加载本地或动态下载的程序集。如果程序集已加载,返回现有实例。

Q4:如何实现一个简单的插件系统?

解析:实现插件系统需要:

  1. 定义一个接口(如IPlugin)在独立的程序集中。
  2. 插件实现该接口并编译为单独的程序集。
  3. 主机应用程序使用Assembly.LoadFrom加载插件程序集,遍历类型,实例化实现IPlugin的类并调用其方法。