C#程序集的定义
程序集是包含一个或多个类型定义文件和资源文件的集合。它允许我们分离可重用类型的逻辑表示和物理表示。
程序集是一个可重用、可实施版本策略和安全策略的单元。它允许我们将类型和资源划分到不同的文件中,这样程序集的使用者便可以决定将哪些文件打包在一起部署。一旦CLR加载了程序集中包含清单的那个文件,它就可以确定程序集的其它文件中哪些包含了程序正在引用的类型和资源。任何程序集的使用者仅需要知道包含清单的文件名称。文件的划分对使用都是透明的,并且可以在将来改变,同时又不会破坏现有的应用程序的行为。
程序集的特性:
1、程序集定义了可重用的类型。
2、程序集标识有一个版本号。
3、程序集可以包含与之相关的安全信息。
二、多文件集:
使用多文件集的三个原因:
1、可以将类型分别实现在不同的文件中,从而允许文件在互联网环境中进行增量下载。
2、可以按需要向程序集中添加资源或数据文件。(数据文件可以是任何格式:文本文件、excel电子表格、word表格、或者任何我们喜欢的格式)。
3、可以使我们创建的程序集包含一些用不同编程语言实现的类型。
三、程序链接器:
程序链接器:Assembly Linker即AL.exe
1、使用前提:
如果我们要创建的程序集包含来自不同的编译器生成的模块,而使用的编译器又不支持类似于C#中/addmodule那样的命令行开关,或者生成模块时还不知道程序集的打包需求,这时程序集链接器就显示非常有用。
四、程序集的分类:
共享程序集:
程序集可以是共享的,也可以是私有的。私有程序集位于应用程序所在的目录下,或其子目录下中。使用私有程序集是,不 需要考虑与其他类的命名冲突或版本冲突问题。在构建过程中引用的程序集会复制到应用程序的目录下。私有程序集是构建程序集的一般方式,特别是应用程序和组件在同一个公司中建立时,就更是如此。
共享程序集:
在使用共享程序集时,必须遵循一些规则。程序集必须是惟一的,因此,必须有一个惟一的名称(称为强名)。该名称的一部分是一个强制的版本号。当组件由另一个开发商构建,而不是应用程序的开发商构建时,以及一个大应用程序分布在几个小项目中时,常常需要使用共享程序集。
========
C#中的程序集和反射介绍
这篇文章主要介绍了C#中的程序集和反射介绍,程序集包含资源文件,类型元数据、IL代码,每个程序集都有自己的名称、版本等信息,反射就是动态获取程序集中的元数据的功能,需要的朋友可以参考下
..什么是程序集?
1.程序集(assembly)是一个及一个以上托管模块,以及一些资源文件的逻辑组合。
2.程序集是组件复用,以及实施安全策略和版本策略的最小单位。
3.程序集是包含一个或者多个类型定义文件和资源文件的集合。在程序集包含的所有文件中,有一个文件用于保存清单。(清单是元数据部分中一组数据表的集合,其中包含了程序集中一部分文件的名称,描述了程序集的版本,语言文化,发布者,共有导出类型,以及组成该程序集的所有文件)。
4、在编译应用程序中,所创建的CIL代码存储在一个程序集中,程序集包括可执行的应用程序文件(.exe扩展名文件)和其他应用程序使用的库(.dll扩展名文件)。
作为一个单元进行版本控制和部署的一个或多个文件的集合。程序集是.NETFramework 应用程序的主要构造块。所有托管类型和资源都包含在某个程序集内,并被标记为只能在该程序集的内部访问,或者被标记为可以从其他程序集中的代码访问。程序集在安全方面也起着重要作用。代码访问安全系统使用程序集信息来确定为程序集中的代码授予的权限集。
程序集是.NET Framework编程的基本组成部分。(此上是在百度百科的定义)
简单的说,net中的dll和exe文件都是程序集
程序集包含资源文件,类型元数据(描述在代码中定义的每一类型和成员,二进制形式)、IL代码(这些都被装在exe或dll中),每个程序集都有自己的名称、版本等信息。这些信息可以通过AssemblyInfo.cs文件来自己定义。
复制代码 代码如下:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的常规信息通过以下
// 特性集控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("AssemblyDemo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AssemblyDemo")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 使此程序集中的类型
// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型,
// 则将该类型上的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("e7da9959-0c97-444c-aa40-6d9bbf728068")]
// 程序集的版本信息由下面四个值组成:
//
// 主版本
// 次版本
// 内部版本号
// 修订号
//
// 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值,
// 方法是按如下所示使用“*”:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
AssemblyInfo.cs
使用程序集的优点:
程序只需要应用必要的程序集,减少了编码量(例如log4net.dll),程序的尺寸 。
可以在程序集中封装一些代码,提供必要的接口,供引用该程序集的项目使用。
反射
提到程序集,就不得不说反射。反射就是动态获取程序集中的元数据(提供程序集的类型信息)的功能。也就是动态获取程序集中的元数据来操作类型的。比如咱们使用的vs中的智能提示,就是通过反射获取类的方法、属性的。
Type类是实现反射的一个很重要的类,通过它我们可以获取类中的所有信息包括方法、属性等。可以动态调用类的属性机及方法。
Type是对类的描述。
一个简单的例子新建一个类库MyAssembly添加Person类,生成:
复制代码 代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyAssembly
{
public class Person
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private string gender;
public string Gender
{
get { return gender; }
set { gender = value; }
}
public Person(string name, int age, string gender)
{
this.name = name;
this.age = age;
this.gender = gender;
}
public void SayHi()
{
Console.WriteLine("大家好,我是{0},今年{1}岁了。",this.name,this.age);
}
}
}
新建一个控制台项目AssemblyDemo,将MyAssembly.dll复制到控制台bin/debug下,接下来通过动态加载程序集MyAssembly.dll获取该程序集下person类的属性和方法。代码如下:
复制代码 代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
namespace AssemblyDemo
{
class Program
{
static void Main(string[] args)
{
//通过反射动态调用另一个程序集中的方法
//1加载程序集
string path = AppDomain.CurrentDomain.BaseDirectory;//获取当前.exe执行文件的路径
path = Path.Combine(path, "MyAssembly.dll");//拼接程序集的路径
Assembly assembly = Assembly.LoadFile(path);
//2创建Person类的对象
Type type = assembly.GetType("MyAssembly.Person");
object o = Activator.CreateInstance(type,"wolf",22,"未知");//实例化
//3获取方法的类型
MethodInfo methodInfo = type.GetMethod("SayHi");
//4反射调用方法
methodInfo.Invoke(o, null);
Console.Read();
}
}
}
结果:
========
C# 应用程序域和程序集
http://blog.csdn.net/zhulongxi/article/details/51612233一、进程Process:
进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法直接访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。
1.进程的建立与销毁:
[csharp] view plain copy
static void Main(string[] args)
{
Process process = Process.Start("notepad.exe","File.txt");
Thread.Sleep(2000);
process.Kill();
}
2.例出计算机运行中的进程:
[csharp] view plain copy
static void Main(string[] args)
{
var processList = Process.GetProcesses()
.OrderBy(x=>x.Id)
.Take(10);
foreach (var process in processList)
Console.WriteLine(string.Format("ProcessId is:{0} \t ProcessName is:{1}",
process.Id, process.ProcessName));
Console.ReadKey();
}
可以通过 GetProcessById 方法获取对应的进程,也可能通过GetProcessByName方法获取多个对应名称的进程
3.获取进程中的所有模块module:这些模块可以是以 *.dll 结尾的程序集,也可以是 *.exe 结尾的可执行程序
[csharp] view plain copy
static void Main(string[] args)
{
var moduleList = Process.GetCurrentProcess().Modules;
foreach (System.Diagnostics.ProcessModule module in moduleList)
Console.WriteLine(string.Format("{0}\n URL:{1}\n Version:{2}",
module.ModuleName,module.FileName,module.FileVersionInfo.FileVersion));
Console.ReadKey();
}
二、程序集:
1.定义:程序集是包含一个或多个类型定义文件和资源文件的集合。它允许我们分离可重用类型的逻辑表示和物理表示。
2.功能:
2.1.包含公共语言运行库执行的代码,如果可移植可执行 (PE) 文件没有相关联的程序集清单,则将不执行该文件中的 Microsoft 中间语言 (MSIL) 代码。请注意,每个程序集只能有一个入口点(即 DllMain、WinMain 或 Main)。
2.2.程序集形成安全边界。程序集就是在其中请求和授予权限的单元
2.3.程序集形成类型边界。每一类型的标识均包括该类型所驻留的程序集的名称。在一个程序集范围内加载的 MyType 类型不同于在其他程序集范围内加载的 MyType 类型。
2.4.程序集形成引用范围边界。程序集的清单包含用于解析类型和满足资源请求的程序集元数据。它指定在该程序集之外公开的类型和资源。该清单还枚举它所依赖的其他程序集。
2.5.程序集形成版本边界。程序集是公共语言运行库中最小的可版本化单元,同一程序集中的所有类型和资源均会被版本化为一个单元。程序集的清单描述您为任何依赖项程序集所指定的版本依赖性。
2.6.程序集形成部署单元。当一个应用程序启动时,只有该应用程序最初调用的程序集必须存在。其他程序集(例如本地化资源和包含实用工具类的程序集)可以按需检索。这就使应用程序在第一次下载时保持精简。
2.7.程序集是支持并行执行的单元.
程序集可以是静态的或动态的。静态程序集可以包括 .NET Framework 类型(接口和类),以及该程序集的资源(位图、JPEG 文件、资源文件等)。静态程序集存储在磁盘上的可移植可执行 (PE) 文件中。您还可以使用 .NET Framework 来创建动态程序集,动态程序集直接从内存运行并且在执行前不存储到磁盘上。您可以在执行动态程序集后将它们保存在磁盘上。
有几种创建程序集的方法。您可以使用过去用来创建 .dll 或 .exe 文件的开发工具,例如 Visual Studio .NET。您可以使用在 .NET Framework SDK 中提供的工具来创建带有在其他开发环境中创建的模块的程序集。您还可以使用公共语言运行库 API(例如 Reflection.Emit)来创建动态程序集。
3.全局程序集缓存:
安装有公共语言运行时的每台计算机都具有称为全局程序集缓存的计算机范围内的代码缓存。 全局程序集缓存中存储了专门指定给由计算机中若干应用程序共享的程序集。
应当仅在需要时才将程序集安装到全局程序集缓存中以进行共享。 一般原则是:程序集依赖项保持专用,并在应用程序目录中定位程序集,除非明确要求共享程序集。 另外,不必为了使 COM 互操作或非托管代码可以访问程序集而将程序集安装到全局程序集缓存
有两种方法可以将程序集部署到全局程序集缓存中:
a. 使用专用于全局程序集缓存的安装程序。 该方法是将程序集安装到全局程序集缓存的首选方法。
b.使用 Windows 软件开发包 (SDK) 提供的名为全局程序集缓存工具 (Gacutil.exe) 的开发工具。
三、应用程序域:
使用.NET建立的可执行程序 *.exe,并没有直接承载到进程当中,而是承载到应用程序域(AppDomain)当中.应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个轻量级的进程。
在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(*.exe)或者多个程序集(*.dll),这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。
当一个程序集同时被多个应用程序域调用时,会出现两种情况:
第一种情况:CLR分别为不同的应用程序域加载此程序集。
第二种情况:CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutral.
1.创建自定义的应用程序域:
当需要应用程序域时,公共语言运行时宿主会自动创建它们。 不过,您可以创建自己的应用程序域并将它们加载到需要亲自管理的程序集中。 您也可以创建从中http://write.blog.csdn.net/postedit?type=edit执行代码的应用程序域。
[csharp] view plain copy
namespace AttributeTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Create new domain");
AppDomain domain = AppDomain.CreateDomain("MyDomain");
Console.WriteLine("Host domain: "+AppDomain.CurrentDomain.FriendlyName);//Host domain: AttributeTest.vshost.exe
Console.WriteLine("Child domain:"+domain.FriendlyName);// Child domain: MyDomain
Console.ReadKey();
}
}
}
2.卸载应用程序域:
当您完成使用应用程序域时,可使用 System.AppDomain.Unload 方法将其卸载。 Unload 方法会正常关闭指定的应用程序域。 卸载过程中,没有新线程可以访问该应用程序域,并且会释放该应用程序域特定的所有数据结构。加载到应用程序域中的所有程序集都会被移除,无法再使用,如果应用程序域中的程序集不是特定于域的,则程序集的数据会保留在内存中,直到整个进程关闭。除了关闭整个进程,没有机制可以卸载非特定于域的程序集。 在某些情况下,卸载应用程序域的请求不起作用,并导致 CannotUnloadAppDomainException。
[csharp] view plain copy
Console.WriteLine("Creating new AppDomain.");
AppDomain domain = AppDomain.CreateDomain("MyDomain", null);
Console.WriteLine("Host domain: " + AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("child domain: " + domain.FriendlyName);
AppDomain.Unload(domain);
try
{
Console.WriteLine();
Console.WriteLine("Host domain: " + AppDomain.CurrentDomain.FriendlyName);
// The following statement creates an exception because the domain no longer exists.
Console.WriteLine("child domain: " + domain.FriendlyName);
}
catch (AppDomainUnloadedException e)
{
Console.WriteLine(e.GetType().FullName);
Console.WriteLine("The appdomain MyDomain does not exist.");
}
Console.ReadKey();
3.配置应用程序域:
可以使用 AppDomainSetup 类为新的应用程序域提供带有配置信息的公共语言运行时,AppDomainSetup的ApplicationBase属性定义应用程序的根目录,当CLR需要满足类型请求时,
它在ApplicationBase属性指定的目录中探测包含该类型的程序集。
[csharp] view plain copy
AppDomainSetup domainInfo = new AppDomainSetup();
domainInfo.ApplicationBase = "C:\Users\v-lozhu\Documents\Visual Studio 2015\Projects\MyTestProjects\AttributeTest";
AppDomain domain = AppDomain.CreateDomain("MyDomain",null,domainInfo);
Console.WriteLine("Host domain: " + AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("child domain: " + domain.FriendlyName);
Console.WriteLine("Application base is: " + domain.SetupInformation.ApplicationBase);
// Unload the application domain.
Console.ReadKey();
4.将程序集加载到应用程序域中:
加载程序集到应用程序域中的方式:
a.Assembly.Load(assemblyName);使用程序集名来加载到应用程序域中,为常用方法。
b.Assembly.LoadFrom():加载给定其文件位置的程序集,通过此方法加载程序集将使用不同的加载上下文。
c.Assembly.ReflectionOnlyLoad和 Assembly.ReflectionOnlyLoadFrom:将程序集加载到仅反射上下文中,可检查(但不能执行)加载到该上下文中的程序集,同时允许检查以其他平台为目标的程序集。
d.AppDomain的CreateInstance和CreateInstanceAndUnwrap方法。
e.Type.GetType()加载程序集
f.AppDomain的Load方法加载程序集。主要用于实现COM互操作性,不应该使用该方法将程序集加载到除从其调用该方法的应用程序域以外的其他应用程序域。
[csharp] view plain copy
namespace AttributeTest
{
class Program
{
static void Main(string[] args)
{
Assembly a = <strong>Assembly.Load</strong>("ExampleTest.exe");
Type myType = a.GetType("ExampleTest.Example");//Name = "Example" FullName = "ExampleTest.Example"
MethodInfo myMethod = myType.GetMethod("MethodA");
object obj = Activator.CreateInstance(myType);
myMethod.Invoke(obj, null);
Console.ReadKey();
}
}
}
[csharp] view plain copy
static void Main(string[] args)
{
var appDomain = AppDomain.CreateDomain("NewAppDomain");
<strong>appDomain.Load</strong>("Model");
foreach (var assembly in appDomain.GetAssemblies())
Console.WriteLine(string.Format("{0}\n----------------------------",
assembly.FullName));
Console.ReadKey();
}
/<p>//当需要在AppDomain加载可执行程序时,可以使用ExecuteAssembly方法。</p>AppDomain.ExecuteAssembly("Example.exe")
[csharp] view plain copy
static void Main(string[] args)
{
var person = (Person<strong>)AppDomain.CurrentDomain.CreateInstance</strong>("Model", "Model.Person").Unwrap();
person.ID = 1;
person.Name = "ddd";
person.Age = 29;
Console.WriteLine(person.Name);
Console.ReadKey();
}
5.从程序集中获得类型和成员信息:
[csharp] view plain copy
Type MyType = Type.GetType("System.IO.BinaryReader");
MemberInfo[] infos = MyType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
Console.WriteLine("members count "+infos.Length);
Console.WriteLine("full name "+MyType.FullName);
foreach (MemberInfo item in infos)
{
Console.WriteLine("\n"+item.Name);
}
Console.ReadKey();
6.影像复制程序集:
公共语言运行时会在加载程序集时锁定程序集文件,因此只有卸载此应用程序域才能更新此程序集,借助卷影复制,无需卸载应用程序域就可更新用于此应用程序域的程序集。
应用程序域被配置为卷影复制时,应用程序路径中的程序集复制到了另一个位置,并从此位置加载,此副本处于锁定状态,但原始程序集文件已解锁并可进行更新。
只有存储在应用程序目录或其子目录中的程序集才可进行卷影复制,这些程序集在配置应用程序域时由 ApplicationBase 和 PrivateBinPath 指定。 存储在全局程序集缓存中的程序集不可进行卷影复制。
6.1.启用卷影复制:
[csharp] view plain copy
AppDomainSetup domainInfo = new AppDomainSetup();
domainInfo.ShadowCopyFiles = "true";
四、.NET 上下文Context:
1.应用程序域是进程中承载程序集的逻辑分区,在应用程序域中存在更细粒度的用于承载.NET对象的实体,即.NET上下文Context.所有的.NET对象都存在于.NET上下文当中,每个AppDomain当中至少存在一个默认上下文(context0),:一般类所建立的对象为上下文灵活对象(context-agile),它们都由CLR自动管理,可存在于任意的上下文当中。
2.透明代理:
在上下文的接口当中存在着一个消息接收器负责检测拦截和处理信息,当对象是MarshalByRefObject的子类的时候,CLR将会建立透明代理,实现对象与消息之间的转换。
应用程序域是CLR中资源的边界,一般情况下,应用程序域中的对象不能被外界的对象所访问。而MarshalByRefObject 的功能就是允许在支持远程处理的应用程序中跨应用程序域边界访问对象,在使用.NET Remoting远程对象开发时经常使用到的一个父类。
3.上下文绑定:
当系统需要对象使用消息接收器机制的时候,即可使用ContextBoundObject类。ContextBoundObject继承了MarshalByRefObject类,保证了它的子类都会通过透明代理被访问。
一般类所建立的对象为上下文灵活对象(context-agile),它们都由CLR自动管理,可存在于任意的上下文当中。而 ContextBoundObject 的子类所建立的对象(称作上下文绑定对象)只能在建立它的对应上下文中正常运行,此状态被称为上下文绑定。其他对象想要访问ContextBoundObject 的子类对象时,都只能通过代透明理来操作。ContextBound还有一个Synchronization特性,此特性会保证ContextBound对象被加载到一个线程安全的上下文当中运行
[csharp] view plain copy
class Program
{
static void Main(string[] args)
{
Example example = new Example();
example.Test();
MycontextBound bouind = new MycontextBound();
bouind.Test("ContextBound Test");
example.Sync(bouind);
Console.ReadKey();
}
public class Example
{
public void Test()
{
ContextMessage("Example test\n");
}
public void Sync(<strong>MycontextBound contextBound</strong>)//传入MyContextBound的代理
{
contextBound.Test("Example call on ContextBound\n");
}
}
[Synchronization]
public class MycontextBound : ContextBoundObject
{
public void Test(string message)
{
ContextMessage(message);
}
}
//显示上下文信息
public static void ContextMessage(string data)
{
Context context = Thread.CurrentContext;
Console.WriteLine(string.Format("{0}ContextId is {1}", data, context.ContextID));
foreach (var prop in context.ContextProperties)
Console.WriteLine(prop.Name);
Console.WriteLine();
}
}
如果在Example类中直接用MycontextBound的示例调用其test方法, 则无法调用,只能通过代理来访问MycontextBound对象。
五、进程、线程、AppDomain、.NET上下文的关系:
1.跨AppDomain运行代码:
在应用程序域之间的数据是相对独立的,当需要在其他AppDomain当中执行当前AppDomain中的程序集代码时,可以使用CrossAppDomainDelegate委托。把CrossAppDomainDelegate委托绑定方法以后,通过AppDomain的DoCallBack方法即可执行委托.
[csharp] view plain copy
class Program
{
static void Main(string[] args)
{
Console.WriteLine("CurrentAppDomain start!");
//建立新的应用程序域对象
AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
CrossAppDomainDelegate crossAppDomainDelegate = new CrossAppDomainDelegate(MyCallBack);
newAppDomain.DoCallBack(crossAppDomainDelegate);
newAppDomain.DomainUnload += (obj, e) =>
{
Console.WriteLine("New Domain unload!");
};
AppDomain.Unload(newAppDomain);
Console.ReadKey();
}
static public void MyCallBack()
{
string name = AppDomain.CurrentDomain.FriendlyName;
for (int n = 0; n < 4; n++)
Console.WriteLine(string.Format(" Do work in {0}........", name));
}
}
2.跨AppDomain的线程:
线程存在于进程当中,它在不同的时刻可以运行于多个不同的AppDomain当中。它是进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时 系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
首先建立一个ConsoleApplication项目,在执行时输入当前线程及应用程序域的信息,最后生成Example.exe的可执行程序。
[csharp] view plain copy
class Program
{
static void Main(string[] args)
{
var message = string.Format(" CurrentThreadID is:{0}\tAppDomainID is:{1}",
Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id);
Console.WriteLine(message);
Console.Read();
}
}
然后再新建一个ConsoleApplication项目,在此项目中新一个AppDomain对象,在新的AppDomain中通过ExecuteAssembly方法执行Example.exe程序。(将Example生成的bin目录下的文件放到当前项目bin目录下):
[csharp] view plain copy
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Current Appdomain start!");
ShowMessage();
//建立新的应用程序域对象
AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
newAppDomain.ExecuteAssembly("Example.exe");
AppDomain.Unload(newAppDomain);
Console.ReadKey();
}
public static void ShowMessage()
{
var message = string.Format(" CurrentThreadID is:{0}\tAppDomainID is:{1}",
Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id);
Console.WriteLine(message);
}
}
输出:
Current Appdomain start!
CurrentThreadID is:8 AppDomainID is:1
CurrentThreadID is:8 AppDomainID is:2
ID等于8的线程在不同时间内分别运行于AppDomain 1与AppDomain 2当中。
4.3.跨上下文的线程:
线程既然能够跨越AppDomain的边界,当然也能跨越不同的上下文。下例,线程将同时运行在默认上下文与提供安全线程的上下文中。
[csharp] view plain copy
class Program
{
static void Main(string[] args)
{
//当前应用程序域信息
Console.WriteLine("CurrentAppDomain start!");
ShowMessage();
//在上下文绑定对象中运行线程
ContextBound contextBound = new ContextBound();
contextBound.Test();
Console.ReadKey();
}
public static void ShowMessage()
{
var message = string.Format(" CurrentThreadID is:{0}\tAppDomainID is:{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentContext.ContextID);
Console.WriteLine(message);
}
[Synchronization]
public class ContextBound : ContextBoundObject
{
public void Test()
{
ShowMessage();
}
}
}
输出:
CurrentAppDomain start!
CurrentThreadID is:10 AppDomainID is:0
CurrentThreadID is:10 AppDomainID is:1
进程(Process)、线程(Thread)、应用程序域(AppDomain)、上下文(Context)的关系如图5.0,一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。线程也能穿梭于多个上下文当中,进行对象的调用
========
C#中的程序集和命名空间
如果说命名空间是类库的逻辑组织形式,那么程序集就是类库的物理组织形式。只有同时指定类型所在的命名空间及实现该类型的程序集,才能完全限定该类型。《精通.NET核心技术--原理与架构》 程序集和命名空间不存在必然联系,一个程序集可以包含多个命名空间,同一个命名空间也可以分放在几个程序集。程序集是应用程序的部署单元。.NET应用程序包含一个或多个程序集。通常扩展名是EXE或DLL 的.NET可执行程序称为程序集。.NET程序集包含元数据,这些元数据描述了程序集中定义的所有类型及其成员的信息,即方法、属性、事件和字段。
在使用共享程序集时,几个应用程序都使用同一个程序集,且与它有一定的依赖关系。共享程序集减少了磁盘和内存空间的需求。使用共享程序集时,要遵循许多规则。共享程序集必须有一个版本号和一个唯一的名称,通常它安装在全局程序集缓存(globd assembly cache,GAC,中。GAC允许共享系统上同一个程序集的不同版本。
在一个程序集中可以有不同的名称空间,同一个名称空间也可以分布在多个程序集上。名称空间只是类型名的一种扩展,它属于类型名的范畴。
还可以这样理解:命名空间就是一个程序集内相关类型的一个分组。举例来讲,System.IO命名空间包含了有关文件I/O的类型,System.Data命名空间定义了基本的数据库类型,等等。需要特别指出的是,一个程序集(比如mscorlib.dll)可以包含任意个命名空间,每个命名空间又可以包含多种类型。
更好的理解程序集和命名空间的关系,可以打开VS里的对象浏览器,可以看到大量的程序集,程序集里又包含了很多不同名的命名空间,同时可以看到一个现象:不同的程序集又包含了同名的命名空间。
===========================================
命名空间与装配件的关系
很多人对这个概念可能还是很不清晰,对于合格的.Net程序员,有必要对这点进行澄清。 装配件是.Net应用程序执行的最小单位,编译出来的.dll、.exe都是装配件。
装配件和命名空间的关系不是一一对应,也不互相包含,一个装配件里面可以有多个命名空间,一个命名空间也可以在多个装配件中存在,这样说可能有点模糊,举个例子:
装配件A:
namespace N1
{
public class AC1 {…}
public class AC2 {…}
}
namespace N2
{
public class AC3 {…}
public class AC4 {…}
}
装配件B:
namespace N1
{
public class BC1 {…}
public class BC2 {…}
}
namespace N2
{
public class BC3 {…}
public class BC4 {…}
}
这两个装配件中都有N1和N2两个命名空间,而且各声明了两个类,这样是完全可以的,然后我们在一个应用程序中引用装配件A,那么在这个应用程序中,我们能看到N1下面的类为AC1和AC2,N2下面的类为AC3和AC4。
接着我们去掉对A的引用,加上对B的引用,那么我们在这个应用程序下能看到的N1下面的类变成了BC1和BC2,N2下面也一样。
如果我们同时引用这两个装配件,那么N1下面我们就能看到四个类:AC1、AC2、BC1和BC2。
到这里,我们可以清楚一个概念了,命名空间只是说明一个类型是那个族的,比如有人是汉族、有人是回族;而装配件表明一个类型住在哪里,比如有人住在北京、有人住在上海;那么北京有汉族人,也有回族人,上海有汉族人,也有回族人,这是不矛盾的。
========
程序集与反射技术(C#)
首先我们来看程序集,程序集是代码进行编译是的一个逻辑单元,把相关的代码和类型进行组合,然后生成PE文件(例如可执行文件.exe和类库文件.dll)。由于程序集在编译后并不一定会生成单个文件,而可能会生成多个物理文件,甚至可能会生成分布在不同位置的多个物理文件,所以程序集是一个逻辑单元,而不是一个物理单元。即程序集在逻辑上是一个编译单元,但在物理储存上可以有多种存在形式。对于静态程序集可以生成单个或多个文件,而动态程序集是存在于内存中的。在C#中程序集处处可见,因为任何基于.NET的代码在编译时都至少存在一个程序集(所有.NET项目都会默认引用mscorlib程序集)。基于.NET框架的.dll库是一个完整的程序集,需要事先引用对应的类库。从代码的结构上看,一个程序集可以包含一个或多个命名空间,而每个命名空间中又可以包含子命名空间或类型列表。由于程序集在编译后可以生成多个模块文件,因此一个物理文件并不代表它就是一个程序集,一个程序集并不一定只有一个文件。不过,在VS开发环境中,一个解决方案可以包含多个项目,而每个项目就是一个程序集。
程序集信息其实是通过在程序集上应用各种Attribute来设置的,并且这些特性都位于System.Reflection命名空间,也就是这一系列Attribute提供给反射技术,可用于获取程序集的基本信息.
应用程序结构分为应用程序域—程序集—模块—类型—成员几个层次,公共语言运行库加载器管理应用程序域,这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。
程序集包含模块,而模块包含类型,类型又包含成员,反射则提供了封装程序集、模块和类型的对象。我们可以使用反射动态地创建类型的实例,将类型绑定到现有对象或从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。反射通常具有以下用途。
(1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
(2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法来调用特定的构造函数。
(4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法来调用特定的方法。
(5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
(6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
(7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
(8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
动态加载程序集:
(1)通过程序集的长名称加载,一般用于加载位于全局程序集缓存中的程序集。(.NET框架的类库都位于全局程序集缓存中)(使用GacUtil命令行工具可以获取System程序集的长名称);
(2)通过指定的文件加载程序集,通常是.dll或.exe文件。
例如:本人的计算机上的全局程序集缓存包含下列程序集:
system, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL
system, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL
项目数 = 2
反射(Reflection)是.NET中的重要机制,通过反射,可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件,以及构造函数等。还可以获得每个成员的名称、限定符和参数等。有了反射,即可对每一个类型了如指掌。如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知道。
1)反射用于获取类型信息。下面一个例子:
复制代码
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using System.Reflection;
7
8 namespace My
9 {
10 class Program
11 {
12 static void Main(string[] args)
13 {
14 // 加载程序集
15 Assembly ass = Assembly.Load("system.windows.forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
16 // 获取Button类的Type
17 Type typeofButton = ass.GetType("System.Windows.Forms.Button", false);
18 if (typeofButton != null)
19 {
20 // 获得Button类型的公共属性列表
21 PropertyInfo[] props = typeofButton.GetProperties();
22 // 输出属性信息
23 Console.WriteLine("\n Button类的公共属性列表:");
24 foreach (PropertyInfo p in props)
25 {
26 Console.WriteLine(" 属性名:{0},属性类型:{1}", p.Name, p.PropertyType.Name);
27 }
28 // 获取Button类型的公共事件列表
29 EventInfo[] events = typeofButton.GetEvents();
30 // 输出事件信息
31 Console.WriteLine("\n Button类的公共事件列表:");
32 foreach (EventInfo ev in events)
33 {
34 Console.WriteLine(" 事件名:{0},事件类型:{1}", ev.Name, ev.EventHandlerType.Name);
35 }
36 }
37 Console.Read();
38 }
39 }
40 }
复制代码
2)反射还可以在运行阶段动态创建类型实例,然后就可以动态调用实例的成员。
动态创建实例有两种方法可以选择:
第一种:使用Activator类的CreateInstance静态方法,其中方法返回的对象就是对应的实例引用。
看个例子:
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace Me
{
class Program
{
static void Main(string[] args)
{
// 从当前执行的程序集中获取Test类型的Type
Assembly ass = Assembly.GetExecutingAssembly();
Type tp = ass.GetType("Me.Test", false);
if (tp != null)
{
// 创建实例
object instance = Activator.CreateInstance(tp, 105);
if (instance != null)
{
// 获取实例的公共属性
PropertyInfo[] props = tp.GetProperties(BindingFlags.Public | BindingFlags.Instance);
// 显示属性信息
foreach (var p in props)
{
Console.WriteLine("{0} : {1}", p.Name, p.GetValue(instance));
}
}
}
Console.Read();
}
}
public class Test
{
public int NumValue { get; private set; }
public Test(int sead)
{
Random rand = new Random(sead);
NumValue = rand.Next();
}
}
}
复制代码
第二种:通过反射找出类型的构造函数,然后调用构造函数来获取实例引用。
代码如下:
复制代码
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6 using System.Reflection;
7
8 namespace My
9 {
10 class Program
11 {
12 static void Main(string[] args)
13 {
14 // 从mscorlib程序集中获取DateTime结构的Type
15 Type tpdatetime = Type.GetType("System.DateTime", false);
16 if (tpdatetime != null)
17 {
18 // 获取具有六个参数的构造函数
19 ConstructorInfo constructor = tpdatetime.GetConstructor(new Type[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int) });
20 if (constructor != null)
21 {
22 // 调用构造函数创建实例
23 object instance = constructor.Invoke(new object[] { 2015, 9, 27, 13, 25, 27 });
24 // 获取实例的属性列表
25 if (instance != null)
26 {
27 PropertyInfo[] props = tpdatetime.GetProperties(BindingFlags.Public | BindingFlags.Instance);
28 Console.WriteLine("以下是DateTime实例的属性值列表:");
29 foreach (PropertyInfo p in props)
30 {
31 // 获取属性值
32 object objval = p.GetValue(instance);
33 // 显示属性名称与属性值
34 Console.WriteLine("{0,-15} :{1}", p.Name, objval ?? string.Empty);
35 }
36 }
37 }
38 }
39
40 Console.Read();
41 }
42 }
43 }
========