水雲涧 發表於 2019-12-29 17:03:00

C#反射与特性(一):反射基础

<h1 id="c反射与特性一反射基础">C#反射与特性(一):反射基础</h1>
<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>C#反射与特性(一):反射基础<ul><li>1. 说明<ul><li>1.1 关于反射、特性</li></ul></li><li>2. 程序集操作<ul><li>2.1 获取 程序集对象(Assembly)<ul><li>2.1.1 运行时获取程序集</li><li>2.1.2 使用方法</li><li>2.1.3 从文件加载程序集</li><li>2.1.4 使用方法</li></ul></li><li>2.2 Assembly 使用<ul><li>2.2.1 获取程序集完全限定名称</li><li>2.2.2 AssemblyName</li></ul></li><li>2.3 获取程序集的方式<ul><li>2.3.1 Assembly.Load()</li><li>2.3.2Assembly.LoadFile()</li></ul></li></ul></li></ul></li></ul></div><p></p>
<h2 id="1-说明">1. 说明</h2>
<h3 id="11-关于反射特性">1.1 关于反射、特性</h3>
<p>在 《C# 7.0 本质论》中,关于这方面的知识在 《第十八章 反射、特性和动态编程》;在《C# 7.0 核心技术指南》中,这部分内容在《第19章 反射和元数据》。</p>
<p><img src="https://img2018.cnblogs.com/blog/1315495/201912/1315495-20191229170120907-84765503.png" alt="" loading="lazy"></p>
<center>[图片来自 《C# 7.0 本质论》]</center>
<p>在这里我们可以获得一些关联性很大的技术:反射、特性、元数据;</p>
<p><strong>元数据</strong>:C# 编写的程序编译成一个程序集,程序集会包含元数据、编译代码和资源。<br>
元数据包含内容:</p>
<ul>
<li>程序或类库中每一个类型的描述;</li>
<li>清单信息,包括与程序本身有关的数据,以及它依赖的库;</li>
<li>在代码中嵌入的自定义特性,提供与特性所修饰的构造有关的额外信息。</li>
</ul>
<p><strong>反射</strong>:在运行时检查并使用元数据和编译代码的操作称为反射。</p>
<p>一个程序集包含的内容:</p>
<p><img src="https://img2018.cnblogs.com/blog/1315495/201912/1315495-20191229170128846-1525414887.png" alt="" loading="lazy"></p>
<center>[图片来自 《C# 7.0 核心技术指南》]</center>
<h2 id="2-程序集操作">2. 程序集操作</h2>
<p>C# 编译成的代码会生成到 .dll 或 .exe 文件中,我们可以通过 Assembly 类,手动加载 程序集文件,实现各种操作。</p>
<p>Assembly 类在 <code>System.Reflection</code> 命名空间中。</p>
<p>《C# 7.0 核心技术指南》中,列出类 Assembly 类常用的属性和方法:</p>
<p><img src="https://img2018.cnblogs.com/blog/1315495/201912/1315495-20191229170134140-158558674.png" alt="" loading="lazy"></p>
<p>接下来我们将通过代码操作,了解 Assembly 的使用方法。</p>
<p>创建一个控制台项目,并设置程序集描述信息。</p>
<p><img src="https://img2018.cnblogs.com/blog/1315495/201912/1315495-20191229170138341-1801713121.png" alt="" loading="lazy"></p>
<h3 id="21-获取-程序集对象assembly">2.1 获取 程序集对象(Assembly)</h3>
<p>微软官方文档建议使用的加载程序集的方式:</p>
<ul>
<li>加载程序集的建议方法是使用 Load 方法,该方法标识要由其显示名称(例如 "b77a5c561934e089,Version = 2.0.0.0,Culture = 中立,PublicKeyToken =")加载的程序集。 该程序集的搜索遵循运行时如何定位程序集中所述的规则。</li>
<li>利用 ReflectionOnlyLoad 和 ReflectionOnlyLoadFrom 方法,你可以加载用于反射的程序集,但不能加载用于执行的程序集。 例如,可通过在32位平台上运行的代码来检查面向64位平台的程序集。</li>
<li>对于程序集必须按路径标识的罕见方案,会提供 LoadFile 和 LoadFrom 方法。</li>
</ul>
<p>一般获取程序集有三种方式:</p>
<ul>
<li>Assembly.Load()</li>
<li>Assembly.LoadFrom()</li>
<li>Assembly.LoadFile()</li>
</ul>
<p>以下方法可以获取到当前程序引用到的程序集:</p>
<pre><code>AppDomain.CurrentDomain.GetAssemblies();
</code></pre>
<p>输出</p>
<pre><code>System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e

ConsoleApp4, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

System.Runtime, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

System.Runtime.Extensions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

System.Console, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
</code></pre>
<h4 id="211-运行时获取程序集">2.1.1 运行时获取程序集</h4>
<p>通过正在运行的类型、函数等形式,去获取程序集。</p>
<pre><code class="language-c#">Assembly 类:
      public static Assembly? GetAssembly(Type type);
   
      public static Assembly GetCallingAssembly();
   
      public static Assembly? GetEntryAssembly();
   
      public static Assembly GetExecutingAssembly();
      
Type 类:
      {type}.Assembly
</code></pre>
<p>解析说明:</p>
<table>
<thead>
<tr>
<th>位置</th>
<th>函数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>Assembly</td>
<td>GetAssembly(Type)</td>
<td>获取在其中定义指定类型的当前加载的程序集</td>
</tr>
<tr>
<td>Assembly</td>
<td>GetCallingAssembly()</td>
<td>返回方法(该方法调用当前正在执行的方法)的 Assembly</td>
</tr>
<tr>
<td>Assembly</td>
<td>GetEntryAssembly()</td>
<td>获取默认应用程序域中的进程可执行文件。 在其他的应用程序域中,这是由 ExecuteAssembly(String)执行的第一个可执行文件</td>
</tr>
<tr>
<td>Assembly</td>
<td>GetExecutingAssembly()</td>
<td>获取包含当前执行的代码的程序集</td>
</tr>
<tr>
<td>Type</td>
<td>Assembly</td>
<td>返回一个类型所在的程序集</td>
</tr>
</tbody>
</table>
<h4 id="212-使用方法">2.1.2 使用方法</h4>
<pre><code class="language-c#">            Assembly assem = typeof(Console).Assembly;
            Assembly ass = Assembly.GetExecutingAssembly();
</code></pre>
<h4 id="213-从文件加载程序集">2.1.3 从文件加载程序集</h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>LoadFrom(String)</td>
<td>已知程序集的文件名或路径,加载程序集</td>
</tr>
<tr>
<td>LoadFrom(String, Byte[], AssemblyHashAlgorithm)</td>
<td>通过给定程序集文件名或路径、哈希值及哈希算法来加载程序集</td>
</tr>
<tr>
<td>LoadFrom(String, Evidence)</td>
<td>在给定程序集的文件名或路径并提供安全证据的情况下,加载程序集</td>
</tr>
<tr>
<td>LoadFrom(String, Evidence, Byte[], AssemblyHashAlgorithm)</td>
<td>通过给定程序集文件名或路径、安全证据、哈希值及哈希算法来加载程序集</td>
</tr>
</tbody>
</table>
<h4 id="214-使用方法">2.1.4 使用方法</h4>
<pre><code class="language-c#">Assembly ass = Assembly.LoadFrom(@"X:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\System.Console.dll");
</code></pre>
<p>另外还有更多中加载程序集的方法,这些方法很偏僻,没必要列出来(因为我不会)。</p>
<h3 id="22-assembly-使用">2.2 Assembly 使用</h3>
<p>获得 Assembly 对象后,就可以进行一系列的骚操作。</p>
<p>常用的 Assembly函数可以查看图三。</p>
<p>先设置两个 Assembly 对象</p>
<pre><code class="language-c#">            Assembly assemA = typeof(Console).Assembly;
            Assembly assemB = Assembly.GetExecutingAssembly();
</code></pre>
<h4 id="221-获取程序集完全限定名称">2.2.1 获取程序集完全限定名称</h4>
<pre><code class="language-c#">            Console.WriteLine("程序集完全限定名");
            Console.WriteLine(assemA.FullName);
            Console.WriteLine(assemB.FullName);
</code></pre>
<pre><code>程序集完全限定名
System.Console, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
ConsoleApp4, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
</code></pre>
<p>里面有个 PublicKeyToken 属性,前面我们介绍了 Assembly 获取程序集的方式,通过 PublicKeyToken ,我们也可以使用 Load 来加载程序集。</p>
<p>但是你可以看到上面的输出, System.Console 有 PublicKeyToken 值,但是自己创建的项目 ConsoleApp4 没有。</p>
<h4 id="222-assemblyname">2.2.2 AssemblyName</h4>
<p>AssmblyName 是用来完整描述程序集的类型。</p>
<p>AssmblyName 是用来获取 程序集 各种信息的类,本身不具有操作功能,仅用于获取程序集的元数据信息。</p>
<p>AssmblyName 实例可以使用 Assembly 的 <code>GetName()</code> 方法获取。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>CodeBase</td>
<td>获取或设置程序集的 URL 位置。</td>
</tr>
<tr>
<td>ContentType</td>
<td>获取或设置指示程序集包含的内容类型的值。</td>
</tr>
<tr>
<td>CultureInfo</td>
<td>获取或设置程序集支持的区域性。</td>
</tr>
<tr>
<td>CultureName</td>
<td>获取或设置与此程序集关联的区域性名称。</td>
</tr>
<tr>
<td>EscapedCodeBase</td>
<td>获取 URI,包括表示基本代码的转义符。</td>
</tr>
<tr>
<td>Flags</td>
<td>获取或设置该程序集的属性。</td>
</tr>
<tr>
<td>FullName</td>
<td>获取程序集的全名(也称为显示名称)。</td>
</tr>
<tr>
<td>HashAlgorithm</td>
<td>获取或设置程序集清单使用的哈希算法。</td>
</tr>
<tr>
<td>KeyPair</td>
<td>获取或设置用于为程序集创建强名称签名的加密公钥/私钥对。</td>
</tr>
<tr>
<td>Name</td>
<td>获取或设置程序集的简单名称。 这通常(但不一定)是程序集的清单文件的文件名,不包括其扩展名。</td>
</tr>
<tr>
<td>ProcessorArchitecture</td>
<td>获取或设置一个值,该值标识可执行文件的目标平台的处理器和每字位数。</td>
</tr>
<tr>
<td>Version</td>
<td>获取或设置程序集的主版本号、次版本号、内部版本号和修订号。</td>
</tr>
<tr>
<td>VersionCompatibility</td>
<td>获取或设置与程序集同其他程序集的兼容性相关的信息。</td>
</tr>
</tbody>
</table>
<pre><code class="language-c#">            AssemblyName assemNameA = assemA.GetName();
            AssemblyName assemNameB = assemB.GetName();

            Console.WriteLine("程序集名称: {0}", assemNameA.Name);
            Console.WriteLine("程序集名称: {0}", assemNameB.Name);

            // 版本
            Console.WriteLine("\nVersion: {0}.{1}",
                assemNameA.Version.Major, assemNameA.Version.Minor);
            Console.WriteLine("Version: {0}.{1}",
    assemNameB.Version.Major, assemNameB.Version.Minor);

            // 程序集的物理文件位置
            Console.WriteLine("\nAssembly CodeBase:{0}", assemA.CodeBase);
            Console.WriteLine("\nAssembly CodeBase:{0}", assemB.CodeBase);
</code></pre>
<p>输出信息</p>
<pre><code>程序集名称: System.Console
程序集名称: ConsoleApp4

Version: 4.1
Version: 1.0

Assembly CodeBase:file:///x:/Program Files/dotnet/shared/Microsoft.NETCore.App/3.0.1/System.Console.dll

Assembly CodeBase:file:///X:/Users/whuanle/source/repos/ConsoleApp4/ConsoleApp4/bin/Debug/netcoreapp3.0/ConsoleApp4.dll
</code></pre>
<p>除了 <code>GetName()</code>,Assembly 类还提供了许多与成员的有关程序集的信息。 例如:</p>
<ul>
<li>GetName 方法返回一个 AssemblyName 对象,该对象提供对程序集显示名称的各个部分的访问。</li>
<li>GetCustomAttributes 方法列出应用于程序集的特性。</li>
<li>GetFiles 方法提供对程序集清单中的文件的访问。</li>
<li>GetManifestResourceNames 方法提供程序集清单中的资源的名称。</li>
</ul>
<h3 id="23-获取程序集的方式">2.3 获取程序集的方式</h3>
<p>上面说到,加载程序集的方式一般使用三种方法:</p>
<ul>
<li>Assembly.Load()</li>
<li>Assembly.LoadFrom()</li>
<li>Assembly.LoadFile()</li>
</ul>
<p>上面已经演示运行时获取和 <code>LoadFrom</code> 两种获取方式。</p>
<p>下面来继续介绍 <code>Assembly.Load()</code> 和 <code>Assembly.LoadFile()</code> 。</p>
<h4 id="231-assemblyload">2.3.1 Assembly.Load()</h4>
<p><code>Assembly.Load()</code> 以强类型的方式去加载程序集,</p>
<p>强名称和程序集签名 指的是 程序集具有唯一的和不可更改的标识。</p>
<p>何以为强类型?通过在清单中添加如下的两种元数据实现:</p>
<ul>
<li>
<p>属于该程序集作者的唯一编号;</p>
</li>
<li>
<p>程序集签名后的散列值,以证实该程序集是由持有其唯一编号的作者生成;</p>
</li>
</ul>
<p>关于这部分内容可以参考 《C# 7.0 核心技术指南》的《18.2 强名称和程序集签名》部分,这里不再赘述。</p>
<p><code>Assembly.Load()</code>加载程序集,同时可以自动加载程序集引用到的其它程序集,并且不会造成重复加载问题。</p>
<p>使用示例:</p>
<pre><code class="language-c#">            Assembly assemA = Assembly.Load("System.Console");
            Assembly assemB = Assembly.Load("ConsoleApp4");
            Assembly assemC = Assembly.Load("System.Console, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
</code></pre>
<p>参考:Assembly.Load 详解(c#)</p>
<p>地址:https://www.cnblogs.com/weifeng123/p/8855629.html</p>
<p>参考:深入了解C#反射中Assembly.Load()、Assembly.LoadFrom()、Assembly.LoadF ile ()方法</p>
<p>地址:https://blog.csdn.net/xuchen_wang/article/details/92773260</p>
<h4 id="232--assemblyloadfile">2.3.2Assembly.LoadFile()</h4>
<p><code>Assembly.LoadFile()</code> 跟<code>Assembly.LoadFrom</code> 的使用方法一致。</p>
<p>区别: <code>Assembly.LoadFile()</code>只会加载指定的一个程序集; <code>Assembly.LoadFrom</code> 会加载一个程序集,然后自动加载此程序集依赖的其它程序集。</p>
<p>此文仅授权《NCC 开源社区》订阅号发布</p>


</div>
<div id="MySignature" role="contentinfo">
    痴者工良(https://whuanle.cn)<br><br>
来源:https://www.cnblogs.com/whuanle/p/12115505.html
頁: [1]
查看完整版本: C#反射与特性(一):反射基础