LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C#中Assembly是.NET框架基本构建模块

admin
2024年9月17日 11:22 本文热度 48

1. 什么是Assembly?

在C#中,Assembly是.NET框架的一个基本构建模块。它可以被看作是一个包含代码和资源的可部署单元,通常以DLL或EXE文件的形式存在。Assembly承载了以下几个关键特性:

  1. 代码封装:Assembly将相关的代码和资源进行封装,是代码逻辑和资源的集合。
  2. 版本控制:每个Assembly都有一个版本号,这对于管理应用程序的不同版本非常重要。
  3. 安全性:Assembly包含安全身份信息,例如强名称签名,可以确保代码的完整性和来源。
  4. 类型信息:Assembly包含元数据,描述了其内部类型和成员,可以被其他代码使用。
  5. 可再分发性:通过将功能模块化,Assembly可以在不同应用程序之间共享和重用。
  6. 依赖管理:Assembly提供了依赖关系的管理,确保应用程序能够正确加载和使用所需的组件。

Assembly分为两种类型:

  • 私有Assembly:仅供单个应用程序使用,通常存放在应用程序的目录中。
  • 共享Assembly:可以被多个应用程序使用,通常存放在全局程序集缓存(GAC)中。

GAC是什么?

GAC,全称为全局程序集缓存(Global Assembly Cache),是.NET框架提供的一个用于存储共享Assembly的特殊文件夹。GAC的主要作用是允许多个应用程序共享使用公共的Assembly,实现代码重用和版本管理。以下是GAC的一些重要特点:

  1. 共享使用:Assembly存放在GAC中后,可以被多个应用程序引用和使用,避免了重复存储和部署。
  2. 版本控制:GAC支持不同版本的同一个Assembly共存,这使得应用程序可以使用不同版本的组件而不产生冲突。
  3. 安全性:只有具有强名称签名的Assembly才能存放在GAC中,强名称签名确保了Assembly的唯一性和完整性。
  4. 管理工具:可以使用命令行工具如gacutil来安装或卸载GAC中的Assembly。

2. 使用场景是什么?

Assembly在C#和.NET开发中有多种使用场景,包括:

  1. 模块化开发:将应用程序分解为多个功能模块,每个模块作为一个独立的Assembly开发和维护。
  2. 代码重用:将通用功能封装成Assembly,以便在不同项目中共享和重用。
  3. 插件架构:使用Assembly实现插件系统,允许动态加载和执行外部组件。
  4. 版本管理:通过Assembly的版本控制机制,支持应用程序的平滑升级和不同版本共存。
  5. 分布式应用:在分布式系统中,将不同服务或组件打包为Assembly,便于部署和管理。
  6. 安全性要求:使用强名称签名的Assembly,确保代码的完整性和来源可信。
  7. 跨语言互操作:通过Assembly提供的元数据支持,允许不同.NET语言之间的互操作。

3. Assembly和AppDomain有什么区别?

在C#和.NET中,Assembly和AppDomain是两个不同的概念,各自承担不同的角色:

Assembly

  1. 基本构建单元:Assembly是.NET应用程序的基本构建模块,包含代码和资源,通常以DLL或EXE文件形式存在。

  2. 模块化和重用:Assembly用于模块化开发和代码重用,可以被多个应用程序共享。

  3. 版本和安全:支持版本管理和强名称签名,确保代码的完整性和来源可信。

  4. 类型信息:包含元数据,描述类型和成员信息,支持反射。

AppDomain

  1. 应用程序隔离:AppDomain是.NET中用于隔离应用程序的执行环境,提供了一个轻量级的进程内隔离机制。

  2. 安全和稳定:在不同AppDomain中运行的代码是相互隔离的,防止错误和崩溃的传播,提高应用程序的稳定性和安全性。

  3. 动态加载和卸载:允许在运行时动态加载和卸载Assembly,不需要重启整个应用程序。

  4. 跨域通信:AppDomain之间可以通过序列化和远程处理进行通信。

区别

  • 作用域:Assembly是代码和资源的物理单位,而AppDomain是逻辑的执行环境。
  • 用途:Assembly用于模块化和重用,AppDomain用于隔离和管理执行。
  • 隔离性:AppDomain提供代码执行的隔离,而Assembly在加载后共享到AppDomain中。

4. Assembly.Load和AppDomain.Load有什么区别?

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.LoadAppDomain.Load用于加载程序集,但它们的使用场景和行为有所不同:

Assembly.Load

  1. 作用域:在当前应用程序域(AppDomain)中加载程序集。
  2. 用法:通常用于在运行时加载程序集,适用于大多数动态加载需求。
  3. 返回值:返回一个Assembly对象,表示已加载的程序集的引用。
  4. 限制:无法跨应用程序域加载程序集,仅限于当前AppDomain。

AppDomain.Load

  1. 作用域:可以在指定的应用程序域中加载程序集。
  2. 用法:常用于需要在特定AppDomain中加载程序集的场景。
  3. 返回值:同样返回一个Assembly对象,但是在指定的AppDomain中加载。
  4. 跨域加载:允许在不同的AppDomain中加载程序集,实现更好的隔离。

区别

  • 加载位置Assembly.Load在当前AppDomain加载,而AppDomain.Load可以指定AppDomain。
  • 隔离性AppDomain.Load提供了更好的隔离,可以在不同的应用程序域中加载程序集。
  • 使用场景Assembly.Load适用于简单的动态加载,AppDomain.Load适用于需要隔离和管理的复杂场景。

什么是System.MarshalByRefObject对象?

System.MarshalByRefObject 是 .NET 框架中的一个基类,允许对象通过引用在应用程序域(AppDomain)之间进行通信。它的主要作用是在跨域场景中支持对象的远程访问。

关键点:

  1. 应用程序域(AppDomain):
    • .NET 中的应用程序域类似于轻量级的进程,用于隔离应用程序。
    • 每个应用程序域都有自己的内存空间和资源,防止不同域之间的直接访问。
  2. Marshal-by-Reference:
    • 默认情况下,对象在不同的应用程序域之间传递时是通过序列化(Marshal-by-Value)进行的。
    • 继承自 MarshalByRefObject 的对象,可以通过引用进行传递,这意味着对象本身并不会被复制到目标域,而是通过代理进行访问。
  3. 远程通信:
    • 适用于需要在不同应用程序域或不同计算机之间进行通信的场景。
    • 典型应用包括远程方法调用(Remoting)。
  4. 生命周期:
    • 继承 MarshalByRefObject 的对象通常会有一个有限的生命周期,由远程调用的服务端来管理。
    • 可以通过覆盖 InitializeLifetimeService 方法来控制对象的生存时间。

使用场景:

  • 在分布式系统中,需要跨域或者跨进程进行通信时。
  • 需要通过远程方法调用访问对象时。

5. CLR为什么不提供卸载?

CLR不提供卸载单独程序集的能力。如果 CLR 允许这样做,那么一旦线程从某个方法返回至已卸载的一个程序集中的代码,应用程序就会崩溃。健壮性和安全性是CLR最优先考虑的目标,如果允许应用程序以这样的一种方式崩溃,就和它的设计初衷背道而驰了。卸载程序集必须卸载包含它的整个AppDomain。使用 ReflectionOnlyLoadFrom或ReflectionOnlyLoad 方法加载的程序集表面上是可以卸载的。毕竟,这些程序集中的代码是不允许执行的。但CLR 一样不允许卸载用这两个方法加载的程序集。因为用这两个方法加载了程序集之后,仍然可以利用反射来创建对象,以便引用这些程序集中定义的元数据。如果卸载程序集,就必须通过某种方式使这些对象失效。无论是实现的复杂性,还是执行速度,跟踪这些对象的状态都是得不偿失的。

总结:

不提供直接卸载单个程序集的功能,主要有以下几个原因:

  1. 内存管理复杂性:卸载单个程序集会增加内存管理的复杂性,可能导致内存泄漏或其他资源管理问题。
  2. 依赖关系:程序集之间可能存在复杂的依赖关系,卸载一个程序集可能会影响其他程序集的正常运行。
  3. 应用程序稳定性:为了确保应用程序的稳定性和一致性,CLR选择不支持单个程序集的卸载。
  4. 替代方案:CLR提供了应用程序域(AppDomain)作为隔离和管理程序集的机制。可以卸载整个AppDomain,从而释放相关的程序集和资源。

6. 反射的性能

太多文章讲解反射的好处和使用这里就不说了直接来看缺点是什么,原因有哪些。

缺点:

  • 反射造成编译时无法保证类型安全性。由于反射严重依赖字符串,所以会丧失编译时类型安全性。例如,执行 Type.GetType(“int”);要求通过反射在程序集中查找名为“int”的类型,代码会通过编译,但在运行时会返回null,因为CLR只知道System.Int32不知int。
  • 反射速度慢。使用反射时,类型及其成员的名称在编译时未知;你要用字符电名称标识每个类型及其成员,然后在运行时发现它们。也就是说,使用System.Reflection命名空间中的类型扫描程序集的元数据时,反射机制会不停地执行字符串搜索。通常,字符串搜索执行的是不区分大小写的比较,这会进一步影响速度。

使用反射调用成员也会影响性能。用反射调用方法时,首先必须将实参打包(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


该文章在 2024/9/18 12:01:20 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved