晚期绑定是一种创建一个给定类型的实例并在运行时调用其成员,而不需要在编译过程中知道它存在的一种技术。当建立一个晚期绑定到外部程序集类型的应用程序时,因为没有设置程序集的引用,因此,调用清单并没有直接列出这个程序集。
System.Activator
System.Activator类是晚期绑定过程中的关键所在,对于我们当前的例子,只需关注Activator.CreateInstance()方法,它用来建立一个晚期绑定类型的实例。为了适应这种情况,CreateInstance()方法经历了多次重载。其中最简单的变化是带有一个有效的Type对象,描述希望动态分配的实体。
为了说明改方法,接下来新建一个名为LateBindingApp的实例。修改Program类:
public class Program { static void Main(string[] args) { Console.writeLine("------"); Assembly a=null; try { a=Assembly.Load("CarLibray"); } catch(FileNotFoundException ex) { console.WriteLine(ex.Message); return; } if(a!=null) { CreateUsingLateBinding(a); } Console.ReadLine(); } static void CreateUsingLateBinding(Assembly a) { try { //得到MiniVan类型的元数据 Type miniVan=a.GetType("CarLibray.MiniVan"); //在运行时建立Minivan object obj=Activator.CreateInstance(miniVan); Console.WriteLine("Create a {0} using late binding",obj); } catch(Exception ex) { Console.WriteLine(ex.Message); } } }
在运行改应用程序之前,需要将CarLibrary.dll手工复制到bin\Debug文件夹下。这是因为我们使用的是Assembly.Load(),因此CLR将只探测客户端文件夹(当然,我们也可以使用LoadForm()向程序集输入一个路径)。说明:不要在本例中使用VS引用CarLibrary.dll,这会在客户端清单上记录改库。晚期绑定的要点是试图创建编译时未知的对象。
注意,Activator.CreateInstance()方法返回一个基本的System.Object类型,而不是一个强类型的MiniVan,因此,如果在obj变量上使用.操作,将不会看到人格MiniVan类的成员。咋一看,我们可以用显示强制类型转换解决这个问题:
object obj=(MiniVan)Activator.CreateInstance(miniVan);
但由于程序并没有引用CarLibrary.dll,我们并不能使用MIniVan。请记住,晚期绑定的重点是建立编译时位置对象的实例。因此,我们如何才能调用存储在System.Object引用中的MiniVan对象的底层方法呢?那么就是反射。
假定希望调用MiniVan中的TurboBoost()方法。回想一下,这个方法设置引擎的状态为"dead"并显示一个消息狂,第一步是使用Type.GetMethod()方法为TurboBoost()方法得到一个MethodInfo对象。接着,可以使用MethodInfo类型的Invoke()方法来调用MiniVan.TurboBoost().MethodInfo.Invoke()需要把所有参数送到MethodInfo代表的方法内。这些参数用一组System.Object类型表示。
Static void CreateUsingLateBinding(Assembly asm) { try { Type miniVan=asm.GetType("CarLibray.MiniVan"); object obj=Activator.CreateInstance(miniVan); MethodInfo mi=miniVan.GetMethod("TurboBoost"); mi.Invoke(obj,null);//null意味着没有参数 } catch(Exception ex) { Console.WriteLine(ex.Message); } }
在使用晚期绑定调用需要参数的方法时,要将参数打包到一个object类型的数组中。zaiCarLibrary.dll的Car类中顶定义了以下方法:
public void TurboRadio(bool musicOn,MusicMedio mm) { if(musicOn) { MessageBox.Show("On"); } else { MessageBox.Show("Off"); } }
改方法包含两个参数:布尔值表示汽车的音乐系统是否关闭,枚举值表示音乐播放器的类别。改枚举结构如下:
public enum MusicMedio { musicCd, musicTape, musicRdio, musicMp3 }
以下是Pragram的新方法:
Static void CreateUsingLateBinding(Assembly asm) { try { Type miniVan=asm.GetType("CarLibray.MiniVan"); object obj=Activator.CreateInstance(miniVan); MethodInfo mi=miniVan.GetMethod("TurboBoost"); mi.Invoke(obj,new object[]{true,2}});//有参数 } catch(Exception ex) { Console.WriteLine(ex.Message); } }
这时,你可以看到、动态加载和晚期绑定之间的关系。当然反射API所提供的特性还有很多。
.NET编译器的任务之一就是为所有定义和引用的类型生成元数据描述。除了程序集中标准的元数据外,.NET平台允许程序员使用特性(attribute)把更多的元数据潜入到程序集中。简言之,特性就是用于类型(比如类、接口、结构等)、成员(属性、方法等)、程序集或模块的代码注解。
使用特性注解代码并不是一个新想法。COM IDL支持大量的预定义特性,他们允许开发者描述包含在给定COM服务器的类型。可是,COM特性只是一些关键字。.NET特性是扩展了抽象的System.Attribute基类的类型。当浏览.NET命名空间时,将发现许多预定义特性。可以在应用程序中使用它们。此外,可以创建自定义特性,通过从Attribute派生出新类型进一步修饰类型的行为。当在代码中应用特性时,如果他们没有被另一个软件显示地反射,那么嵌入的元数据基本上没有什么作用。反之。嵌入的程序集中的元数据介绍可以忽略不计,并无害处。
为了举例说明在C#中使用特性的过程,先创建一个控制台程序。创建一个Motorcycle的类,他可以被持久化为二进制格式。要实现它,只需要在类的定义中应用[Serializable]特性。如果有一个字段不想被持久化,可以使用[NonSerialized]特性:
[Serializable] public class Motorcycle { [NonSerialized] float weight; bool hasRadio; }
一个特性只能被应用在接下来的对象,举例来说,在Motocycle类中不能被序列化的字段仅是weight,而由于整个类中都可以序列化,所以其他字段都可以被序列化。
构建自定义特性
第一步是建立一个新的派生自System.Attribute的类。
public sealed class VehicleAttribute:System.Attribute { public string Description{get;set;} public VehicleAttribute(string vehicaleDescription) { Description=vehicaleDescription; } public VehicleAttribute(){} }
public sealed class VehicleAttribute:System.Attribute { public string Description{get;set;} public VehicleAttribute(string vehicaleDescription) { Description=vehicaleDescription; } public VehicleAttribute(){}
可见,VehicleAttribute使用自动属性来维护一个字符串数据。该类除了派生自Attribute之外,定义没有什么特别。处于安全原因,应该把所有特性定义成密封的。
应用自定义特性
[Serializable] [VehicleAttribute(Description="my car")] public class Motorcycle { }
限制特性的使用:默认情况下,自定义特性可以被应用在代码中几乎所有的方面。因此,只要恰当,可以使用VehicleAttribute修饰方法、属性、字段。
[VehicleAttribute(" A very long")] public class Winnebago { [VehicleAttribute("My CD")] public void Palymusic(bool on) {} }
在某些情况下,这样就能满足需要。但是,有时候也许像建立这样一个自定义特性:它只被应用到选定的代码元素上。如果需要限制自定义特性的应用范围,需要在自定义特性的定义中应用[AttributeUsage]特性。这个特性支持AttributeTargts枚举值的任意集
public enum AttributeTargrts { All,Assembly,Class,Constructor,Delegate,Enum,Event,Field, Interface,Method,Module,Parameter,Property }
此外,[AttributeUsage]也允许我们任意设置命名属性,比如AllowMultiple,它用来指示在相同项上特性是否能被应用多次,而[AttributeUsage]也允许我们使用Inherited命名属性指示特性能否被派生类继承(默认为true),为了设定[VehicleAttribute]特性只能在类或结构中使用一次,可以修改定义如下:
[AttributeUsage(AttributeTargrts.Class|AttributeTargrts.Struct,Inherited=false)] public sealed class VehicleAttribute:System.Attribute { public string Description{get;set;} public VehicleAttribute(string vehicaleDescription) { Description=vehicaleDescription; } public VehicleAttribute(){} }
span style="color: #008000;">//分别使用[module:]和[assembly:]标签,在给定模块的所有类型(对于多文件程序集)或给定程序集所有模块中应用特性也是可以的,举例来说,加入希望所有定义在程序集中的每个公共类型都符合CLS的。 [assembly:CLSCompliant(true)] namespace AttributeCarlibrary { } //如果不符合则会收到一个编译器错误 public class Winnnebago { public ulong notCompli;(未标记数据的公开点) } //使用早期绑定反射特性:记住,一个特性直到另一个软件反射它的值时才有效。给定的特性被发现后,软件可以采取任何需要的行为。现在,和应用程序一样,这样“另一个软件”也可以使用早期绑定找到一个自定义特性。如果使用早期绑定,则相应的特性需要客户应用程序在编译时定义,既然AttributeCarLibrary程序集把这个自定义特性定义为公共类,早期绑定是最好的选择。 using AttributeCarlibrary; namespace VehicleAttributeReader { static void Main() { ReflectionAttribute(); } private void ReflectionAttribute() { Type t=typeof(Winnnebago); object[] customAtts=t.GetCustomAttributes(false); foreach(VehicleAttribute V in customAtts) { console.WriteLine(V.Description); } } } //Type.GetCustomAttributes()方法返回一个对象数组,表示了应用到Type代表的成员上的所有特性。 //使用晚期绑定反射特性:首先,将AttributeCarLibrary.dll复制到项目的\bin\Debug文件夹下。这是因为我们使用的是Assembly: using AttributeCarlibrary; namespace VehicleAttributeReader { static void Main() { ReflectionAttribute(); } private void ReflectionAttribute() { try { Assembly asm=Assembly.Load("AttributeCarlibrary"); Type vahicleDesc=asm.GetType("AttributeCarlibrary.VehicleAttribute"); PropertyInfo propDesc=vahicleDesc.GetProperty("Description"); Type[] types=asm.GetTypes(); foreach(Type t in types) { object[] objs=t.GetCustomAttributes(vahicleDesc,false); foreach(object o in objs) { console.WriteLine(t.Name,propDesc.GetValue(o,null)); } } } catch(Exception ex) { Console.WriteLine(ex.Message); } } }