美文网首页
C#动态编程

C#动态编程

作者: 书上得来终觉浅 | 来源:发表于2018-09-03 16:44 被阅读67次

    元数据是在程序编译过程中创建并嵌入到程序集中,利用反射技术可以读取程序的元数据,可以做以下操作:

    • 获取类型的成员,包含变量和函数
    • 实例化一个对象
    • 执行对象的成员函数
    • 获取类型的信息
    • 获取程序集的信息
    • 获取自定义特征
    • 创建和编译新程序集

    自定义特征

    预定义的许多特征,C#编译器都是支持的,也就是说,编译器可以在编译阶段识别并解读这些特征,然后根据这些特征的预定义规则去定制编译过程。而用户自定义的特征,编译器明显是不能识别的,不过编译器会将其作为元数据加在对应的元素上,嵌入到程序集中。特征转化为元数据后,我们就可以利用反射读取这些元数据,使程序在运行期间做出决策。说的通俗一点,自定义特征可能影响程序的逻辑走向。

    
    //自定义一个特征,名称为FieldNameAttribute,可以作用于属性元素上,有一个必填的属性为name
    
    //AttributeTargets是个枚举值,表示特征能用于什么元素上,这里设置为可以用于属性和字段上
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
    public class FieldNameAttribute:Attribute{
        public string Name {get; private set;};
        public string Scoped {get;set;}
        public FieldNameAttribute(string name){
            this.Name = name;
        }
    }
    
    public class Person
    {
        /*
        将FeildNameAttribute加载Name属性项,
        因为FieldNameAttribute的构造函数中有name参数,
        所以这里的“名字”,并没有加上name="...",而是直接赋的值
        */
        [FeildName("名字",Scpoed ="可选项")]
        public string Name { get; set; }
    
        public Person()
        {
        }
    
        public void foo(){
            Attribute info = Attribute.GetCustomAttribute(typeof(Person),typeof(FeildNameAttribute));
            FeildNameAttribute attr = (FeildNameAttribute)info;
            Console.WriteLine(attr.Name);
            Console.WriteLine(attr.Scoped);
        }
    }
    

    特征类本身会用一个预定义特征(System.AttributeUsage)来标记它是一个特征。特征类有了这个AttributeUsage特征后,编译器就会为它做特殊处理。

    反射

    上面讲的自定义特征,仅仅是为了给反射铺路。提到反射就必须要知道System.Type类。Type是访问元数据的主要方式,使用Type提供的方法可以获得指定类型的信息,比如获取一个类定义的构造函数,方法,属性,字段,事件等信息,甚至还以获得这个类依赖的模块和程序集等。可以使用Type描述的类型有:Class,Value type,Array,Interface,Enumeration,Delegate,generic type(由泛型创建的对象)。获取一个Type对象有以下3种方法:

    • typeof()运算符,typeof运算符中加要获取的类型的类型名,如:typeof(string)
    • Object.GetType(),这个方法是通过实例对象获取该对象的Type类型,如 var a = 0; a.GetType();
    • Type.GetType(),这个是Type类型提供的静态方法,Type type = Type.GetType("Demo.Person");

    这里需要注意的是Type是abstract类型的,所以不能被实例化,我们通过上面的方式获得的Type实例,其实是程序在运行时根据具体的类型生成的Type派生类的实例。

    Type实例获取的反射数据大致分为两类,第一类是描述类型本身的属性,比如类型的名称Name,类型的命名空间Namespace,完全限定名FullName等。第二类是类型内的元素,包括构造函数,方法,属性,字段等信息。

    返回的对象类型 方法 描述
    MemberInfo GetMember() 获取成员,包括属性,字段,方法,事件等
    ConstructorInfo GetConstructor() 构造函数
    MethodInfo GetMethod() 方法
    EnventInfo GetEvnet() 事件
    PropertyInfo GetProperty() 属性
    FieldInfo GetField() 字段

    以上介绍的都是使用反射读取类型的元数据,有了这些元数据后就可以实例化对象并调用其方法,以下是一些调用思路。

    //获取Foo的公共无参构造函数,并实例化该对象
    var foo = typeof(Foo).GetConstructors()
                        .ToList()
                        .Where(c => c.IsPublic && c.GetParameters().Length==0)
                        .First()?.Invoke(null);
    
    //上面的列子可以进一步泛化,创建T类型的无参构造实例
    
    /// <summary>
    /// 创建T类型的无参构造实例
    /// </summary>
    public T CreateInstance<T>(){
        var result = typeof(T).GetConstructors()
                             .ToList()
                             .Where(c => c.IsPublic && c.GetParameters().Length == 0)
                             .First()?.Invoke(null);
        return (T)result;
    }
    
    //如果要创建有参的实例也是可以的,只是要稍微麻烦一点,需要获取指定与参数列表中参数类型和个数相同的构造函数,这里代码未写
    
    //获取foo的方法并调用
    typeof(Foo).GetMethod("Bar").Invoke(foo, new object[] { /* params */ });
    

    动态类型

    动态编程的实质是程序在编译时,不告诉编译器要创建什么类型的实例,等到程序运行时,动态的创建特定类型的实例和调用该实例的方法。

    dynamic类型使编译器在编译期间忽略类型检查,dynamic对象与var关键字不同,var是编译器在编译期间反推对象的类型,也就是说,用var关键字定义的对象时,编译后会有明确的数据类型的,而dynamic对象编译后不知道是什么类型,它可以在运行期间任意的改变类型。

    var a = 1;
    a = "Hello World";//编译错误,a已经是int类型。
    
    dynamic b = 1;
    b = "Hello World"; //程序在运行时,动态的将b的类型修改为string类型
    

    相关文章

      网友评论

          本文标题:C#动态编程

          本文链接:https://www.haomeiwen.com/subject/yqkiwftx.html