在C#中,Assembly是.NET框架的一个基本构建模块。它可以被看作是一个包含代码和资源的可部署单元,通常以DLL或EXE文件的形式存在。Assembly承载了以下几个关键特性:
Assembly分为两种类型:
GAC是什么?
GAC,全称为全局程序集缓存(Global Assembly Cache),是.NET框架提供的一个用于存储共享Assembly的特殊文件夹。GAC的主要作用是允许多个应用程序共享使用公共的Assembly,实现代码重用和版本管理。以下是GAC的一些重要特点:
gacutil
来安装或卸载GAC中的Assembly。Assembly在C#和.NET开发中有多种使用场景,包括:
在C#和.NET中,Assembly和AppDomain是两个不同的概念,各自承担不同的角色:
Assembly
AppDomain
区别
System.AppDomain 提供了 Load方法。和Assembly 的静态Load 方法不同,AppDomaim的Load是实例方法,它允许将程序集加载到指定的AppDomain 中。该方法设计由非托管代码调用,允许宿主将程序集“注入”特定 AppDomain 中。托管代码的开发人员一般情况下不应调用它,因为调用 AppDomaim 的Load 方法时需要传递一个标识了程序集的字符串。该方法随后会应用策略,并在一些常规位置搜索程序集。我们知道,AppDomain 关联了一些告诉 CLR如何查找程序集的设置。为了加载这个程序集,CLR 将使用与指定AppDomain 关联的设置,而非与发出调用之AppDomain 关联的设置。但AppDomain 的 Load 方法会返回对程序集的引用。由于System.Assembly类不是从System.MarshalByRefObject派生的,所以程序集对象必须按值封送回发出调用的那个AppDomain。但是,现在CLR就会用发出调用的那个 AppDomain 的设置来定位并加载程序集。如果使用发出调用的那个 AppDomain 的策略和搜索位置找不到指定的程序集,就会抛出一个 FileNotFoundException。这个行为一般不是你所期望的,所以应该避免使用 AppDomain 的 Load 方法。
一台机器可能同时存在具有相同标识的多个程序集。由于重要提示LoadFrom会在内部调用 Load,所以CLR有可能不是加载你指定的文件而是加载一个不同的文件,从而造成非预期的行为。强烈建议每次生成程序集时都更改版本号,确保每个版本都有自己的唯一性标识,确保LoadFrom方法的行为符合预期。除此之外Assembly.Load
和AppDomain.Load
用于加载程序集,但它们的使用场景和行为有所不同:
Assembly.Load
Assembly
对象,表示已加载的程序集的引用。AppDomain.Load
Assembly
对象,但是在指定的AppDomain中加载。区别
Assembly.Load
在当前AppDomain加载,而AppDomain.Load
可以指定AppDomain。AppDomain.Load
提供了更好的隔离,可以在不同的应用程序域中加载程序集。Assembly.Load
适用于简单的动态加载,AppDomain.Load
适用于需要隔离和管理的复杂场景。什么是System.MarshalByRefObject对象?
System.MarshalByRefObject
是 .NET 框架中的一个基类,允许对象通过引用在应用程序域(AppDomain)之间进行通信。它的主要作用是在跨域场景中支持对象的远程访问。
关键点:
MarshalByRefObject
的对象,可以通过引用进行传递,这意味着对象本身并不会被复制到目标域,而是通过代理进行访问。MarshalByRefObject
的对象通常会有一个有限的生命周期,由远程调用的服务端来管理。InitializeLifetimeService
方法来控制对象的生存时间。使用场景:
CLR不提供卸载单独程序集的能力。如果 CLR 允许这样做,那么一旦线程从某个方法返回至已卸载的一个程序集中的代码,应用程序就会崩溃。健壮性和安全性是CLR最优先考虑的目标,如果允许应用程序以这样的一种方式崩溃,就和它的设计初衷背道而驰了。卸载程序集必须卸载包含它的整个AppDomain。 使用 ReflectionOnlyLoadFrom或ReflectionOnlyLoad 方法加载的程序集表面上是可以卸载的。毕竟,这些程序集中的代码是不允许执行的。但CLR 一样不允许卸载用这两个方法加载的程序集。因为用这两个方法加载了程序集之后,仍然可以利用反射来创建对象,以便引用这些程序集中定义的元数据。如果卸载程序集,就必须通过某种方式使这些对象失效。无论是实现的复杂性,还是执行速度,跟踪这些对象的状态都是得不偿失的。
总结:
不提供直接卸载单个程序集的功能,主要有以下几个原因:
太多文章讲解反射的好处和使用这里就不说了直接来看缺点是什么,原因有哪些。
缺点:
使用反射调用成员也会影响性能。用反射调用方法时,首先必须将实参打包(pack)成数组:在内部,反射必须将这些实参解包(unpack)到线程栈上。此外,在调用方法前,CLR 必须实参具有正确的数据类型。最后,CLR必须确保调用者有正确的安全权限来访问被调用的成员。 好上述所有原因,最好避免利用反射来访问字段或调用方法/属性。应该利用以下两种技权一开发应用程序来动态发现和构造类型实例。
示例代码:
using System.Diagnostics;
using System.Reflection;
namespace AssemblyDemo;
class Program
{
static void Main(string[] args)
{
// 创建测试对象
var testObject = new TestClass();
// 测试直接调用
Stopwatch directStopwatch = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
testObject.SimpleMethod();
}
directStopwatch.Stop();
Console.WriteLine($"直接调用时间: {directStopwatch.ElapsedMilliseconds} ms");
// 获取方法信息
MethodInfo methodInfo = typeof(TestClass).GetMethod("SimpleMethod");
// 测试反射调用
Stopwatch reflectionStopwatch = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
methodInfo.Invoke(testObject, null);
}
reflectionStopwatch.Stop();
Console.WriteLine($"反射调用时间: {reflectionStopwatch.ElapsedMilliseconds} ms");
}
}
class TestClass
{
private int _counter = 0;
public void SimpleMethod()
{
// 增加计数器
_counter++;
}
}
运行结果:
直接调用时间: 1 ms
反射调用时间: 10 ms