【.NET】反射和特性

晚期绑定

晚期绑定是一种创建一个给定类型的实例并在运行时调用其成员,而不需要在编译过程中知道它存在的一种技术。当建立一个晚期绑定到外部程序集类型的应用程序时,因为没有设置程序集的引用,因此,调用清单并没有直接列出这个程序集。

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编译器的任务之一就是为所有定义和引用的类型生成元数据描述。除了程序集中标准的元数据外,.NET平台允许程序员使用特性(attribute)把更多的元数据潜入到程序集中。简言之,特性就是用于类型(比如类、接口、结构等)、成员(属性、方法等)、程序集或模块的代码注解。
    
      使用特性注解代码并不是一个新想法。COM IDL支持大量的预定义特性,他们允许开发者描述包含在给定COM服务器的类型。可是,COM特性只是一些关键字。.NET特性是扩展了抽象的System.Attribute基类的类型。当浏览.NET命名空间时,将发现许多预定义特性。可以在应用程序中使用它们。此外,可以创建自定义特性,通过从Attribute派生出新类型进一步修饰类型的行为。当在代码中应用特性时,如果他们没有被另一个软件显示地反射,那么嵌入的元数据基本上没有什么作用。反之。嵌入的程序集中的元数据介绍可以忽略不计,并无害处。

在C#中使用特性

      为了举例说明在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特性来注释自定义特性
    [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);

            }

        }

    }

 

你可能感兴趣的:(.net)