c#特性与反射及IO文件操作
第六章 高级编程
1.特性(特征)
特性(attribute)是一种允许我们向程序的程序集添加元数据的语言结构.它是用于保存程序结构信息的某种特殊类型的类.
特性提供功能强大的方法,用以将元数据或声明信息与代码(程序集、类型、方法、属性等)相关联。特性与程序实体关联后,
即可在运行时使用名为“反射”的技术查询特性。
将应用了特性的程序结构叫做目标.
设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者
.NET预定了很多特性,我们也可以声明自定义特性.
Obsolete特性(方法前):
可以使用Obsolete特性将程序结构标注为过期的,并且在代码编译时,显示警告信息.
格式:[ Obsolete( "警告信息", true)]//最后一个参数为true表示这个方法不能被调用了,如果调用则出错
Conditional特性(方法前):
为方法声明应用Conditional特性并把编译符作为参数来使用
是否屏蔽特定方法的调用,如果参数的字符串定义了宏则调用,如果没有定义则不调用
System.Diagnostics;//所需命名空间
格式: # define hong //如果定义了宏则调用,如果没有定义则不调用(宏声明在最外层).
[ Conditional ( "hong" )]
在方法上使用Conditional特性的规则:
【1】该方法必须是类或结构体的方法;
【2】该方法必须为void类型;
【3】该方法不能被声明为override,但可以标记为virtual;
【4】该方法不能是接口方法的实现。
DebuggerStepThrough特性(方法前)
可以跳过Debugger的单步调试,不让进入该方法的调试(当我们确定这个方法没有任何错误的时候,可以使用这个)
格式:[ DebuggerStepThrough ]
调用者信息特性(参数前):
using System.Runtime.CompilerServices;//导入命名空间
[ CallerFilePath ]:调用者的文件路径
[ CallerLineNumber ]:调用者代码行数
[ CallerMemberName ]:调用者成员或方法名称
//如果调用方法时显式指定了这些参数,则使用真正的参数值;如果没有显式提供参数值,则系统将会提供文件路径、代码行数、方法成员名称。
(其他特性)
[ CLSCompliant ]:声明可公开的成员应该被编译器检查是否符合CLS.兼容的程序集可以被任何.NET兼容的语言使用
[ Serializable] :声明结构可以被序列化.
[ NonSerialized ]:声明结构不可以被序列化
[ DLLImport ]:声明是非托管代码实现的.
[ WebMethod ]:声明方法应该被作为XML Web服务的一部分暴露
[ AttributeUsage ]:声明特性能应用到什么类型的程序结构.将这个特性应用到特性声明上.
自定义特性的规范:
命名规范:特性名使用Pascal命名法(首字母大写),并且以Attribute后缀结尾,当为目标应用特性时,我们可以不使用后缀。
例如对于SerializableAttrbute和MyAttributeAttribute这两个特性,我们把他们应用到结构是可以使用Serializable和MyAttribute。
定义:
1)特性类后缀以Attribute结尾
2)需要继承于System.Attribute
3)特性类是不需要被继承的,一般声明为sealed
4)一般情况下特性类用来表示目标结构的一些状态(只定义一些字段或属性,一般不定义方法)
定义或控制特性的使用:
AttributeUsage类是另外一个预定义特性类,它帮助我们控制我们自己的定制特性的使用。它描述了一个定制特性如和被使用。
AttributeUsage有三个属性,我们可以把它放置在定制属性前面。
ValidOn
通过这个属性,我们能够定义定制特性应该在何种程序实体前放置。一个属性可以被放置的所有程序实体在AttributeTargets enumerator中列出。
通过OR操作我们可以把若干个AttributeTargets值组合起来。 我们可以使用AttributeTargets.All来允许特性被放置在任何程序实体前。
AllowMultiple
这个属性标记了我们的定制特性能否被重复放置在同一个程序实体前多次。
Inherited
我们可以使用这个属性来控制定制特性的继承规则。表明当特性被放置在一个基类上时,基类的这个特性能否被派生类所继承。
一个声明上可放置多个特性;某些特性对于给定实体可以指定多次。
例如,ConditionalAttribute 就是一个可多次使用的特性:[Conditional("DEBUG"), Conditional("TEST1")]
例:
//表示该特性类可以应用于哪些程序结构 这里表示应用于类,不可在程序实体前使用多次,基类该特性不可被继承
[ AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class MytestAtrribute:System.Attribute
{
public string Description{get; set;} //属性
public string VersionNumber{get; set;}
public string ReviewerID{get; set;}
//除了属性外,不可实现公有方法或其他函数成员
public MytestAtrribute(string desc,string ver) //构造函数
{
Description = desc;
VersionNumber = ver;
}
}
[Mytest( " ")] //将括在方括号中的特性名置于其应用到的实体的声明上方
class Program
{
****
}
详细: https://blog.csdn.net/c38623862/article/details/65443975
https://www.cnblogs.com/liuxinxin/articles/2265672.html
https://www.cnblogs.com/rohelm/archive/2012/04/19/2456088.html
访问特性:
【1】使用IsDefined方法
可以使用Type对象的IsDefined方法来检测某个特性是否应用到了某个类上。
MyClass mc = new MyClass(); //创建类的对象
Type t = mc.GetType(); //使用从Object基类继承的GetType方法获取Type对象的一个引用
bool isDefined = t.IsDefined(typeof(MytestAtrribute),false); //调用IsDefined方法来判断特性是否应用在了这个类上
//第一个参数:接受需要检查的特性的Type对象
//第二个参数:bool类型,指示是否搜索该类的继承树来查找这个特性
【2】使用GetCustomAttribute方法
Type类的GetCustomAttribute方法返回应用到结构上的特性的数组。
Type t = typeof(MyClass);
object[] attr = t.GetCustomAttribute(false); //布尔参数指定是否搜索该类的继承树来查找这个特性
foreach(Attribute ar in attr)
{
MytestAtrribute attrl = (MytestAtrribute)ar; //实际返回的对象是object数组,必须强制转换为相应的特性类型
}
2.元数据
元数据是有关程序及其类型的数据。
是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件 (PE) 文件或存储在内存中的程序进行描述。将您的代码编译为 PE 文件时,便会将元数据插入到该文件的一部分中,而将代码转换为 Microsoft中间语言(MSIL)并将其插入到该文件的另一部分中。
在模块或程序集中定义和引用的每个类型和成员都将在元数据中进行说明。当执行代码时,运行库将元数据加载到内存中,
并引用它来发现有关代码的类、成员、继承等信息。
详细:https://www.cnblogs.com/Peter-Luo/archive/2012/05/31/2528889.html
3.反射
运行中的程序查看本身的元数据或其他程序的元数据的行为叫作反射。使用反射,必须使用System.Reflection命名空间。
这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:
(1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
(2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
(4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
(5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
(6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
(7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
(8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
【1】反射的作用:
1)可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型
2)应用程序需要在运行时从某个特定的程序集中载入一个特定的类型,以便实现某个任务时可以用到反射。
3)反射主要应用于类库,这些类库需要知道一个类型的定义,以便提供更多的功能。
【2】有3种获取给定类型的type引用:
1. Type t = typeog(string);
2. string s = "grayworm";
Type t = s.GetType();
3. Type t = Type.GetType("system.string");
【3】Type类方法:
GetConstructor(), GetConstructors() //返回ConstructorInfo类型,用于取得该类的构造函数的信息
GetEvent(), GetEvents() //返回EventInfo类型,用于取得该类的事件的信息
GetField(), GetFields() //返回FieldInfo类型,用于取得该类的字段(成员变量)的信息
GetInterface(), GetInterfaces() //返回InterfaceInfo类型,用于取得该类实现的接口的信息
GetMember(), GetMembers() //返回MemberInfo类型,用于取得该类的所有成员的信息
GetMethod(), GetMethods() //返回MethodInfo类型,用于取得该类的方法的信息
GetProperty(), GetProperties() //返回PropertyInfo类型,用于取得该类的属性的信息
NewClassw nc = new NewClassw(); Type t = nc.GetType(); ConstructorInfo[] ci = t.GetConstructors(); //获取类的所有构造函数 foreach (ConstructorInfo c in ci) //遍历每一个构造函数 { ParameterInfo[] ps = c.GetParameters(); //取出每个构造函数的所有参数 foreach (ParameterInfo pi in ps) //遍历并打印所该构造函数的所有参数 { Console.Write(pi.ParameterType.ToString()+" "+pi.Name+","); } Console.WriteLine(); }
【4】用反射生成对象,并调用属性、方法和字段进行操作
NewClassw nc = new NewClassw(); Type t = nc.GetType(); object obj = Activator.CreateInstance(t); //取得ID字段 FieldInfo fi = t.GetField("ID"); //给ID字段赋值 fi.SetValue(obj, "k001"); //取得MyName属性 PropertyInfo pi1 = t.GetProperty("MyName"); //给MyName属性赋值 pi1.SetValue(obj, "grayworm", null); PropertyInfo pi2 = t.GetProperty("MyInfo"); pi2.SetValue(obj, "hi.baidu.com/grayworm", null); //取得show方法 MethodInfo mi = t.GetMethod("show"); //调用show方法 mi.Invoke(obj, null);
【5】System.Reflection.Assembly类
通过程序集名称返回Assembly对象
Assembly ass = Assembly.Load("ClassLibrary831");
通过DLL文件名称返回Assembly对象
Assembly ass = Assembly.LoadFrom("ClassLibrary831.dll");
通过Assembly获取程序集中类
Type t = ass.GetType("ClassLibrary831.NewClass"); //参数必须是类的全名
通过Assembly获取程序集中所有的类
Type[] t = ass.GetTypes();
详细:
https://www.cnblogs.com/Stephenchao/p/4481995.html
https://www.cnblogs.com/vaevvaev/p/6995639.html
https://www.cnblogs.com/yaozhenfa/p/CSharp_Reflection_1.html
4.文件及数据流
文件:一些具有永久存储及特定顺序的字节组成的一个有序的、具有名称的集合。
流:提供一种向后备存储写入字节和从后备存储读取字节的方式。
1)File类
支持对文件的基本操作,包括用于创建、复制、删除、移动和打开文件的静态方法,并协助创建FileStream对象。
Copy //将现有文件复制到新文件
Create //在指定路径中创建文件
Delete //删除指定文件,若指定文件不存在,则不引发异常
Exists //确定指定文件是否存在
Move //将指定文件移动到新位置,并提供指定新文件名的选项
Open //打开指定路径上的FileStream
GetCreationTime //返回指定文件或目录的创建日期和时间
GetLastAccessTime //返回上次访问文件或目录的日期和时间
GetLastWriteTime //返回上传写入指定文件或目录的日期和时间
OpenRead //打开现有文件进行读取
OpenWrite //打开现有文件进行写入
Replace //使用其他文件的内容替换指定文件的内容,这一过程将删除原始文件,并创建被替换文件的备份
//使用File类进行文件操作 class Program { static void Main(string[] args) { //在D盘下创建code文件夹 Directory.CreateDirectory("D:\\code"); Directory.CreateDirectory("D:\\code-1"); string path = "D:\\code\\test1.txt"; //创建文件 FileStream fs = File.Create(path); File.WriteAllText("D:\\code\\test1.txt", "qwertyuiop", System.Text.Encoding.UTF8); File.ReadAllText(path, System.Text.Encoding.UTF8); //获取文件信息 Console.WriteLine("文件创建时间:" + File.GetCreationTime(path)); Console.WriteLine("文件最后被写入时间:" + File.GetLastWriteTime(path)); //关闭文件流 fs.Close(); //设置目标路径 string newPath = "D:\\code-1\\test1.txt"; //判断目标文件是否存在 bool flag = File.Exists(newPath); if (flag) { //删除文件 File.Delete(newPath); } File.Move(path, newPath); } }
2)Directory类
CreatDirectory//创建文件夹
Delete//删除文件夹
//使用Directory类创建和删除文件夹 class Program { static void Main(string[] args) { bool flag = Directory.Exists("D:\\code"); if (flag) { Directory.Delete("D:\\code", true); } else { Directory.CreateDirectory("D:\\code"); } } }
3)数据流
在此主要讲FileStream(文件流)的使用
常用属性:
CanRead //获取一个值,该值指示当前流是否支持读取
CanWrite //获取一个值,该值指示当前流是否支持写入
CanSeek //获取一个值,该值指示当前流是否支持查找
CanTimeout //获取一个值,该值指示当前流是否可以超时
IsAsync //获取一个值,该值指示文件流是异步还是同步打开
Length //获取用字节表示的流长度
Name //获取传递给构造函数的文件流的名称
Position //获取或设置此流的当前位置
ReadTimeout //获取或设置一个值,该值确定流在超时前尝试读取多长时间
WriteTimeout //获取或设置一个值,该值确定流在超时前尝试写入多长时间
常用方法:
BeginRead //开始异步读操作
BeginWrite //开始异步写操作
Close //关闭当前流并释放与之关联的所有资源
EndRead //等待挂起的异步读取完成
EndWrite //结束异步写入,在I/O操作完成之前一直阻止
Lock //允许读取访问的同时防止其他进程更改文件流
UnLock //允许其他进程访问以前锁定的某个文件的全部或部分
Read //从流中读取字节块并将该数据写入给定的缓冲区中
Write //使用从缓冲区读取的数据将字节块写入该流
ReadByte //从文件中读取一个字节,并将读取的位置提升一个字节
WriteByte //将一个字节写入文件流的当前位置
Seek //将流的当前位置设置为给定值
SetLength //将该流的长度设置为给定值
FileMode类的枚举成员:
Append //打开现有文件并查找到文件尾,只能同FileAccess.Write一起使用
Create //指定操作系统创建新的文件,如果文件已存在,则被改写
CreateNew //指定操作系统创建新的文件,如果文件已存在,则引发IOException
Open //指定操作系统打开现有文件。打开文件的能力取决于FileAccess的值
OpenOrCreate //指定操作系统创建新的文件,如果文件已存在,否则应创建新文件
Truncate //指定操作系统打开现有文件,文件一旦打开,就将被截断为零字节大小
文本文件的写入与读取
[1]StreamWriter类
属性:
Encoding //获取将输出写入其中的Encoding中
Formatprovider //获取控制格式设置的对象
NewLine //获取或设置由当前TextWriter使用的行结束符字符串
方法:
Close //关闭当前的StringWriter和基础流
Write //写入StringWriter的此实例中
WriteLine //写入重载参数指定的某些数据,后面跟行结束符
//StreamWriter类的使用 class Program { static void Main(string[] args) { string path = @"D:\\code\\test.txt"; //创建StreamWriter 类的实例 StreamWriter streamWriter = new StreamWriter(path); //向文件中写入姓名 streamWriter.WriteLine("小张"); //向文件中写入手机号 streamWriter.WriteLine("13112345678"); //刷新缓存 streamWriter.Flush(); //关闭流 streamWriter.Close(); } }
[2]StreamReader类
方法:
Close //关闭StringReader
Read //读取输入字符串中的下一个字符或下一组字符
ReadBlock //从当前流中读取最大count的字符并从index开始将该数据写入buffer
ReadLine //从基础字符串中读取一行
ReadToEnd //将整个流或从流的当前位置到流的结尾作为字符串读取
//StreamReader 类的应用 class Program { static void Main(string[] args) { //定义文件路径 string path = @"D:\\code\\test.txt"; //创建 StreamReader 类的实例 StreamReader streamReader = new StreamReader(path); //判断文件中是否有字符 while (streamReader.Peek() != -1) { //读取文件中的一行字符 string str = streamReader.ReadLine(); Console.WriteLine(str); } streamReader.Close(); } }
二进制文件的写入与读取
[1]BinaryWriter类
方法:
Close //关闭当前的BinaryWriter和基础流
Seek //设置当前流的位置
Write //将值写入当前流
[2]BinaryReader类
方法:
Close //关闭当前阅读器及基础流
PeekChar //返回一个可用字符,并且不提升字节或字符的位置
Read //从基础流中读取字符,并提升流的当前位置
ReadBoolean //从当前流中读取Boolean值,并使该流当前位置提升一个字节
ReadByte //从当前流中读取下一个字节,并使当前位置提升一个字节
ReadBytes //从当前流中读取count个字节放入字节数组,并使当前位置提升count个字节
ReadChar //从当前流中读取下一个字符,并根据所使用的Encoding和从流中读取特定的字符,提升流当前的位置
ReadChars //从当前流中读取count个字符,以字符数组形式返回数据,并根据所使用的Encoding和从流中读取特定的字符,提升流当前的位置
ReadInt32 //从当前流中读取4个字节有符号整数,并使流的当前位置提升4个字节
ReadString //从当前流中读取一个字符串,字符串有长度前缀,一次将7位编码为整数
版本控制软件:Git VSS TFS SVN
4)Git
https://docs.microsoft.com/zh-cn/visualstudio/version-control/git-with-visual-studio?view=vs-2019
5)VSS
Visual Source Safe,简称VSS。它提供了还原点和并行协作功能,从而使应用程序开发组织能够同时处理软件的多个版本。
该版本控制系统引入了签入和签出模型,按照该模型,单个开发人员可以签出文件,进行修改,然后重新签入该文件。当文件被签出后,
其他开发人员通常无法对该文件进行更改。通过源代码管理系统,开发人员还能够回滚或撤消任何随后产生问题的更改。
作为一种版本控制系统,Visual Source Safe 能够:
• 防止用户无意中丢失文件。
• 允许回溯到以前版本的文件。
• 允许分支、共享、合并和管理文件版本。
• 跟踪整个项目的版本。
• 跟踪模块化代码(一个由多个项目重用或共享的文件)。
https://blog.csdn.net/buknow/article/details/82841603
6)NuGet引用第三方库
https://www.cnblogs.com/dathlin/p/7705014.html
7)利用反射实现一个工厂类
https://blog.csdn.net/silangquan/article/details/51100823
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)