C#中的反射和特性

Posted ProMer_Wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#中的反射和特性相关的知识,希望对你有一定的参考价值。

C#中的反射和特性(二)

上一节C#中的反射和特性(一),简单的了解了一下反射是什么,以及部分的特性,接下来继续来了解C#中的其他特性。

1.Assembly类

Assembly类在System.Reflection命名空间中定义,它允许访问给定程序集的元数据,它也包含了可以加载和执行程序集。

例如我们在上一章节所讲的内容的基础上加上以下代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Test_Reflection_and_characteristic
{
    class Program
    {
        static void Main(string[] args)
        {
            //Myclass my = new Myclass();//每一个类对应一个type对象,这个type对象存储了这个类,有哪些方法跟哪些方法以及哪些成员
            //Type testType = my.GetType();//一个类中的数据是存储在对象中的,但是type对象只存储类的成员
            //Console.WriteLine("输出 testType所引用的命名空间:{0}", testType);
            //Console.WriteLine("获取当前testType所引用的类名:{0}", testType.Name);
            //Console.WriteLine("输出当前tesType中的程序集:{0}", testType.Assembly);
            //Console.WriteLine("输出与当前testType相关联的属性:{0}", testType.Attributes);        
通过FieldInfo去访问字段
        //FieldInfo[] array = testType.GetFields();
        //foreach (FieldInfo info in array)
        //{
        //    //注意只有public公有的字段才能访问到,private私有字段是无法访问到的
        //    Console.WriteLine("输出当前testType中所含有的字段{0}", info.Name);
        //}

        //PropertyInfo[] array2 = testType.GetProperties();
        //foreach (PropertyInfo info in array2)
        //{
        //    //注意只有public公有的属性才能访问到,private私有属性是无法访问到的
        //    Console.WriteLine("输出当前testType中所含有的属性{0}", info.Name);
        //}

        //MethodInfo[] array3 = testType.GetMethods();
        //foreach (MethodInfo info in array3)
        //{
        //    //注意只有public公有的方法才能访问到,private私有方法是无法访问到的
        //    Console.WriteLine("输出当前testType中所含有的方法{0}", info.Name);
        //}  
        Myclass my = new Myclass();
        Assembly myassem = my.GetType().Assembly;//通过类的type对象获取它所在的程序集 Assembly
        Console.WriteLine("输出获取到的程序集的名称:{0}",myassem.FullName);
        Type[] types = myassem.GetTypes();
        foreach (var type in types)
        {
            Console.WriteLine("输出我们type所引用的程序集中类有:{0}",type);
        }
        Console.ReadKey();

    }
}
}

运行结果:

我们就会发现我们程序及里面所含有的类的名称都被打印了出来

1.1.如何加载程序集?

1,Assembly assembly1 = Assembly.Load(“SomeAssembly”);根据程序集的名字加载程序集,它会在本地目录和全局程序集缓存目录查找符合名字的程序集。
2,Assembly assembly2 = Assembly.LoadFrom(@“c:\\xx\\xx\\xx\\SomeAssembly.dll”)//这里的参数是程序集的完整路径名,它不会在其他位置搜索。

1.2Assembly对象的使用

1,获取程序集的全名 string name = assembly1.FullName;
2,遍历程序集中定义的类型 Type[] types = theAssembly.GetTypes();
foreach(Type definedType in types){
//
}
3,遍历程序集中定义的所有特性
Attribute[] definedAttributes = Attribute.GetCustomAttributes(someAssembly);

2.什么是特性?

特性(attribute)是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。
将应用了特性的程序结构叫做目标
设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者
.NET预定了很多特性,我们也可以声明自定义特性

2.1创建和使用特性

我们在源代码中将特性应用于程序结构;
编译器获取源代码并且从特性产生元数据,然后把元数据放到程序集中;
消费者程序可以获取特性的元数据以及程序中其他组件的元数据。注意,编译器同时生产和消费特性。

关于特性的命名规范,特性名使用Pascal命名法(首字母大写),并且以Attribute后缀结尾,当为目标应用特性时,我们可以不使用后缀。例如对于SerializableAttribute和MyAttributeAttribute这两个特性,我们把他们应用到结构是可以使用Serializable和MyAttribute。

2.2 应用特性

先看看如何使用特性。特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。我们可以通过把特性应用到结构来实现。
在结构前放置特性片段来应用特性;
特性片段被方括号包围,特性片段包括特性名和特性的参数列表;
应用了特性的结构成为特性装饰。

案例1
[Serializable] //特性
public class MyClass{
// …
}
案例2
[MyAttribute(“Simple class”,“Version 3.57”)] //带有参数的特性
public class MyClass{
//…
}

3.Obsolete特性->.NET预定义特性

一个程序可能在其生命周期中经历多次发布,而且很可能延续多年。在程序生命周期的后半部分,程序员经常需要编写类似功能的新方法替换老方法。处于多种原因,你可能不再使用哪些调用过时的旧方法的老代码。而只想用新编写的代码调用新方法。旧的方法不能删除,因为有些旧代码也使用的旧方法,那么如何提示程序员使用新代码呢?可以使用Obsolete特性将程序结构标注为过期的,并且在代码编译时,显示有用的警告信息。
class Program{
[Obsolete(“Use method SuperPrintOut”)] //将特性应用到方法
static void PrintOut(string str){
Console.WriteLine(str);
}
[Obsolete(“Use method SuperPrintOut”,true)]//这个特性的第二个参数表示是是否应该标记为错误,而不仅仅是警告。
static void PrintOut(string str){
Console.WriteLine(str);
}
static void Main(string[] args){
PrintOut(“Start of Main”);
}
}

Test_Character.class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test_Character
{
    class Program
    {
        [Obsolete("这个方法过时了,使用NewMethod来代替")]//将特性应用到方法,obsolete特性用来表示一个方法
        //[Obsolete("这个方法过时了,使用NewMethod来代替", false)]//这个特性加上一个bool值,当bool值为true时,会使得该方法出现时作报错处理
        static  void OldMethod()
        {
            Console.WriteLine("OldMethod");
        }
        static void NewMethod()
        {
            Console.WriteLine("NewMethod");
        }
        static void Main(string[] args)
        {
            OldMethod();
        }
    }
}

4.Conditional(方法、属性的忽略)特性

Conditional特性允许我们包括或取消特定方法的所有调用。为方法声明应用Conditional特性并把编译符作为参数来使用。
定义方法的CIL代码本身总是会包含在程序集中,只是调用代码会被插入或忽略。
#define DoTrace

class Program{
[Conditional(“DoTrace”)]
static void TraceMessage(string str){
Console.WriteLine(str);
}
static void Main(){
TraceMessage(“Start of Main”);
Console.WriteLine(“Doing work in Main.”)
TraceMessage(“End of Main”);
}
}

注意:

Conditonal特性所在的命名空间在:

using System.Diagnostics;//Conditional所在的命名空间

所以我们在使用这个特性的时候一定不要忘了引用对应的命名空间

假设我们有两个静态全局的方法,Test1,Test2,当我们对两个方法进行调用的时候,会输出两个结果,但当我们使用了Conditional并且通过宏来设置,就可以忽略掉我们用Contional所标记的函数了。

例如:

//#define IsTest//定义了一个宏来指定我们标记的函数,注释掉就不会调用了,解开注释后调用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;//Conditional所在的命名空间

namespace Test_Character
{
    class Program
    {
        [Conditional("IsTest")]
        static void Test1()
        {
            Console.WriteLine("Test1");
        }
        static void Test2()
        {
            Console.WriteLine("Test2");
        }
       
        static void Main(string[] args)
        {
            Test1();
            Test2();
            Test1();
        }
    }
}

注释宏的输出结果:

解开注释的宏的输出结果:

Test1和Test2的方法依旧会被编译到程序里面,只是编译器会去检测有没有定义对应的宏,如果定义了宏就不会去调用Test1方法,如果没有定义指定的宏,就会去调用Test1方法。

5.调用者信息特性

调用者信息特性可以访问文件路径,代码行数,调用成员的名称等源代码信息。
这个三个特性名称为CallerFilePath,CallerLineNumberCallerMemberName

表示调用者的文件:CallerFilePath

表示调用者的代码行数:CallerLineNumber

表示调用者的方法成员名称:CallerMemberName

这些特性只能用于方法中的可选参数
例如:

#define IsTest//定义了一个宏,注释掉就不会调用了
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.CompilerServices;//Conditional所在的命名空间

namespace Test_Character
{
    class Program
    {
       public static void PrintOut(string message,[CallerFilePath] string filename="",
           [CallerLineNumber]int lineNumber = 0,[CallerMemberName]string callingMember="")
       {
	    Console.WriteLine("Message:"+message);
	    Console.WriteLine("Line :"+lineNumber);
	    Console.WriteLine("Called from:"+callingMember);
	    Console.WriteLine("Message :"+message);
        }
        static void Main(string[] args)
        {
            PrintOut("123");
        }
    }
}

运行结果如下:

6.联系工作实际:

其实上面的内容,也是以前博主通过网上的学习进行总结的。目前呢,因为博主所负责的主要的工作职责,就跟项目的相关打包流程相关,其中大部分就利用到了C#和大量的文件操作之间的知识,所以博主可能在完成部分的章节后可能通过自己的学习以及通过工作中的实践,后期打算从打包工具、扩展工具、AssetBundle、总结打包原理以及流程、以及打包相关的知识去给大家做介绍。不过这个周期可能也会相对较长,一方面工作的原因可能无法长时间抽出太多的时间来做写文章,另一方面,在对框架的理解上去做总结的话,我还是更愿意等真的能够准确无误的陈述出来的时候,我才考虑将打包流程的详细过程通过文章表达出来,一方面不想误导粉丝,另一方面为了文章的效率去复制内容,还不如真真实实的用自己的方式去表达出来表达的更加容易理解。

7.杂谈:

以前了解过朋友的这么一句话,CSDN里面的大佬很多,优秀文章也有很多,每个人的水平不一样,能够表达出来的效果也不一样,但是通过文章的形式呈现出自己的表达就已经很不错了。

所以,如果你喜欢这种简简单单的,章节性的内容的陈述,或者当作是工作日常的一种朋友圈,欢迎关注啊。

如果博客博友对这个章节有更多的内容想要了解的,可以评论哈,等这一个章节结束后,我会根据评论反馈以及自己的能力,将评论中反馈的内容在下一个计划中去制作出对应的内容的哈~

作者:ProMer_Wang

链接:https://blog.csdn.net/qq_43801020/article/details/120279253

本文为ProMer_Wang的原创文章,著作权归作者所有,转载请注明原文出处,欢迎转载!

以上是关于C#中的反射和特性的主要内容,如果未能解决你的问题,请参考以下文章

C#中的反射和特性

C#探索之路:反射和特性

C#中的反射和特性

C#中的反射和特性

C# 9 新特性:代码生成器编译时反射

C# 9 预览版发布,新特性:代码生成器编译时反射