目录
一、C# 异常处理
1、try/catch语句
2、C# 中的异常类
3、自定义异常类
4、C# 抛出异常
二、C# 目录操作
1、DirectoryInfo 类
2、FileInfo 类
三、C# 文件读写
1、C# 中的 I/O 类
2、FileStream 类
3、C# 中文本文件的读取/写入
(1)StreamReader
(2)StreamWriter
4、C# 中二进制文件读写
(1)BinaryReader 类
(2)BinaryWriter 类
四、C# 特性
1、定义特性
2、预定义特性
(1)AttributeUsage
(2)Conditional
(3)Obsolete
3、自定义特性
(1)声明自定义特性
(2)构建自定义特性
(3)应用自定义特性
五、C# 反射
1、反射的用途
2、查看元数据
六、C# 属性
1、访问器
2、抽象属性
七、C# 索引器
1、定义索引器
2、索引器重载
# .NET API
在 C# 中,异常是在程序运行出错时引发的,所有异常都派生自 System.Exception 类。异常处理就是处理运行时错误的过程,通过异常处理可以使程序在发生错误时保持正常运行。
C# 中的异常处理基于四个关键字构建,分别是:try、catch、finally 和 throw。
假设一段代码会引发异常,则可以使用 try 和 catch 组合来捕获这个异常。举例如下:
try
{
int a = 123;
int b = 0;
int x = a / b;
}
catch (Exception e)
{
Console.WriteLine("捕获到的异常:{0}", e);
}
finally
{
Console.WriteLine("finally 语句块中的代码!");
}
Console.ReadKey();
C# 中的异常类主要是从 System.Exception 类派生的。比如 System.ApplicationException 和 System.SystemException 就是从 System.Exception 类派生的。
下表中列举了一些从 Sytem.SystemException 类派生的预定义异常类:
异常类 | 描述 |
---|---|
System.IO.IOException | 处理 I/O 错误 |
System.IndexOutOfRangeException | 处理当方法引用超出数组范围的索引时产生的错误 |
System.ArrayTypeMismatchException | 处理当数组类型不匹配时产生的错误 |
System.NullReferenceException | 处理引用一个空对象时产生的错误 |
System.DivideByZeroException | 处理当除以零时产生的错误 |
System.InvalidCastException | 处理在类型转换期间产生的错误 |
System.OutOfMemoryException | 处理空闲内存不足产生的错误 |
System.StackOverflowException | 处理栈溢出产生的错误 |
除了可以使用系统预定义的异常类外,我们还可以自行定义异常类,自定义的异常类都应继承 System.ApplicationException 类。
class Program
{
static void Main(string[] args)
{
TestUserDefinedException test = new TestUserDefinedException();
try
{
test.Validate(12);
} catch (InvalidAgeException e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey();
}
}
public class InvalidAgeException : ApplicationException
{
public InvalidAgeException(string message) : base(message)
{
}
}
public class TestUserDefinedException
{
public void Validate(int age)
{
if (18 > age)
{
throw new InvalidAgeException("Sorry, Age must be greater than 18");
}
}
}
如果异常是直接或间接派生自 System.Exception 类,则可以在 catch 语句块中使用 throw 语句抛出该异常。throw 语句的语法格式如下所示:
catch (Exception e)
{
......
throw e
}
C# 中允许您使用各种目录和文件相关的类来操作目录和文件,比如 DirectoryInfo 类和 FileInfo 类。
DirectoryInfo 类派生自 FileSystemInfo 类。其中提供了各种用于创建、移动、浏览目录和子目录的方法。需要注意的是,该类不能被继承。
下表列出了 DirectoryInfo 类中一些常用的属性和方法:
属性/方法 | 描述 |
---|---|
Attributes | 获取当前文件或目录的属性 |
CreationTime | 获取当前文件或目录的创建时间 |
Exists | 获取一个表示目录是否存在的布尔值 |
Extension | 获取表示文件扩展名部分的字符串 |
FullName | 获取目录或文件的完整路径 |
LastAccessTime | 获取当前文件或目录最后被访问的时间 |
Name | 获取此 DirectoryInfo 实例的名称 |
public void Create() | 创建一个目录 |
public DirectoryInfo CreateSubdirectory(string path) | 在指定的路径上创建子目录,指定的路径可以是相对于 DirectoryInfo 类的实例的路径 |
public override void Delete() | 如果为空的,则删除该 DirectoryInfo |
public DirectoryInfo[] GetDirectories() | 返回当前目录的子目录 |
public FileInfo[] GetFiles() | 从当前目录返回文件列表 |
FileInfo 类派生自 FileSystemInfo 类,其中提供了用于创建、复制、删除、移动、打开文件的属性和方法。与 DirectoryInfo 类相同,FileInfo 类也不能被继承。
下表列出了 FileInfo 类中一些常用的属性和方法:
属性 | 描述 |
---|---|
Attributes | 获取当前文件的属性 |
CreationTime | 获取当前文件的创建时间 |
Directory | 获取文件所属目录的一个实例 |
Exists | 获取一个表示文件是否存在的布尔值 |
Extension | 获取表示文件存在的字符串 |
FullName | 获取文件的完整路径 |
LastAccessTime | 获取当前文件最后被访问的时间 |
LastWriteTime | 获取文件最后被写入的时间 |
Length | 获取当前文件的大小,以字节为单位 |
Name | 获取文件的名称 |
public StreamWriter AppendText() | 创建一个 StreamWriter,追加文本到由 FileInfo 的实例表示的文件中 |
public FileStream Create() | 创建一个文件 |
public override void Delete() | 永久删除一个文件 |
public void MoveTo(string destFileName) | 移动一个指定的文件到一个新的位置,提供选项来指定新的文件名 |
public FileStream Open(FileMode mode) | 以指定的模式打开一个文件 |
public FileStream Open(FileMode mode,FileAccess access) | 以指定的模式,使用 read、write 或 read/write 访问,来打开一个文件 |
public FileStream Open(FileMode mode,FileAccess access,FileShare share) | 以指定的模式,使用 read、write 或 read/write 访问,以及指定的分享选项,来打开一个文件 |
public FileStream OpenRead() | 创建一个只读的 FileStream |
public FileStream OpenWrite() | 创建一个只写的 FileStream |
举例:
// 创建一个 DirectoryInfo 对象
DirectoryInfo dir = new DirectoryInfo(@"C:\Users\Administrator\Desktop\cs");
Console.WriteLine("获取当前目录的属性: {0}", dir.Attributes);
Console.WriteLine("获取当前目录的创建时间: {0}", dir.CreationTime);
Console.WriteLine("获取当前目录是否存在: {0}", dir.Exists);
Console.WriteLine("获取当前文件扩展名部分的字符串: {0}", dir.Extension);
Console.WriteLine("获取当前目录的完整路径: {0}", dir.FullName);
Console.WriteLine("获取上次访问当前目录的时间: {0}", dir.LastAccessTime);
Console.WriteLine("获取 DirectoryIndiro 实例的名称: {0}", dir.Name);
// 获取目录中的文件以及它们的名称和大小
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
Console.WriteLine("文件名称:{0} ,大小:{1}", file.Name, file.Length);
}
文件是存储在磁盘中的具有特定名称和目录路径的数据集合。当我们使用程序对文件进行读取或写入时,程序会将文件以数据流(简称流)的形式读入内存中。流可以看作是通过通信路径传递的字节序列,主要分为输入流和输出流,输入流主要用于从文件读取数据(读操作),输出流主要用于向文件中写入数据(写操作)。
System.IO 命名空间中包含了各种用于文件操作的类。如下表所示:
I/O 类 | 描述 |
---|---|
BinaryReader | 从二进制流中读取原始数据 |
BinaryWriter | 以二进制格式写入原始数据 |
BufferedStream | 临时存储字节流 |
Directory | 对目录进行创建、删除、移动等操作(静态类) |
DirectoryInfo | 用于对目录执行操作 |
DriveInfo | 获取驱动器的信息 |
File | 对文件进行操作 |
FileInfo | 用于对文件执行操作 |
FileStream | 用于文件中任何位置的读写 |
MemoryStream | 用于随机访问存储在内存中的数据流 |
Path | 对路径信息执行操作 |
StreamReader | 用于从字节流中读取字符 |
StreamWriter | 用于向一个流中写入字符 |
StringReader | 用于从字符串缓冲区读取数据 |
StringWriter | 用于向字符串缓冲区写入数据 |
FileStream 类在 System.IO 命名空间下,使用它可以读取、写入和关闭文件。创建 FileStream 类对象的语法格式如下所示:
FileStream
= new FileStream( , , , ); 参数说明如下:
- object_name:对象名称;
- file_name:文件路径(包含文件名);
- FileMode:枚举类型,用来指定文件的打开方式,可选值如下:
- Append:打开一个已有文件,并将光标放置在文件末尾。如果文件不存在,则创建文件;
- Create:创建一个新文件。如果文件已存在,则将旧文件删除,然后创建新文件;
- CreateNew:创建一个新的文件。如果文件已存在,则抛出异常;
- Open:打开一个已有文件。如果文件不存在,则抛出异常;
- OpenOrCreate:打开一个已有文件,如果文件不存在,则创建一个新的文件并打开;
- Truncate:打开一个已有文件,然后将文件清空(删除原有内容)。如果文件不存在,则抛出异常;
- FileAccess:枚举类型,用来设置文件的存取,可选值如下:
- Read:对文件的读取访问权限,可以从文件读取数据;
- Write:对文件的写入访问权限,数据可以写入该文件;
- ReadWrite:对文件的读取和写入访问权限,可以写入和从文件中读取数据;
- FileShare:枚举类型,用来设置文件的权限,可选值如下:
- Inheritable:使文件句柄由子进程继承,这是不直接支持 Win32;
- None:拒绝共享当前文件。在关闭文件之前,任何请求打开的文件(此进程或另一个进程)将失败;
- Read:允许以后打开文件进行读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求都将失败。需要注意的是,即使指定了此标志,仍需要附加权限才能够访问该文件;
- Write:允许以后打开文件进行写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求都将失败,需要注意的是,即使指定了此标志,仍可能需要附加权限才能够访问该文件;
- ReadWrite:允许以后打开的文件进行读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求都将失败,需要注意的是,即使指定了此标志,仍需要附加权限才能够访问该文件;
- Delete:允许以后删除文件;
FileStream 类中的常用方法如下所示:
方法 | 描述 |
---|---|
Close() | 关闭当前流并释放与之关联的所有资源(如套接字和文件句柄) |
CopyTo(Stream) | 从当前流中读取字节并将其写入到另一流中 |
Dispose() | 释放由 Stream 使用的所有资源 |
Finalize() | 确保垃圾回收器回收 FileStream 时释放资源并执行其他清理操作 |
Flush() | 清除此流的缓冲区,使得所有缓冲数据都写入到文件中 |
Lock(Int64, Int64) | 防止其他进程读取或写入 FileStream |
Unlock(Int64, Int64) | 允许其他进程访问以前锁定的某个文件的全部或部分 |
Read(Byte[], Int32, Int32) | 从流中读取字节块并将该数据写入指定缓冲区 |
ReadByte() | 从文件中读取一个字节,并将读取位置提升一个字节 |
Write(Byte[], Int32, Int32) | 将字节块写入文件流 |
WriteByte(Byte) | 将一个字节写入文件流中的当前位置 |
Equals(Object) | 判断指定对象是否等于当前对象 |
ToString() | 返回表示当前对象的字符串 |
GetHashCode() | 默认哈希函数 |
GetType() | 获取当前实例的 Type |
举例:
// 使用 FileStream 类读取指定的文件:
FileStream file = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);
for (int i = 0; i < 20; i++)
{
file.WriteByte((byte)i);
}
// 设置文件流的位置
file.Position = 0;
for (int i = 0; i < 20; i++)
{
Console.Write(file.ReadByte() + " ");
}
file.Close();
Console.ReadKey();
System.IO 命名空间下的 StreamReader 和 StreamWriter 类可以用于文本文件的读写操作。
StreamReader 类继承自抽象基类 TextReader,用于从特定编码的字节流中读取字符。下表列出了 该类中一些常用的方法:
方法 | 描述 |
---|---|
public override void Close() | 关闭 StreamReader 对象和基础流,并释放任何与之相关的系统资源 |
public override int Peek() | 返回下一个可用的字符,但不使用它 |
public override int Read() | 从输入流中读取下一个字符,并把字符位置往前移一个字符 |
举例:
使用 StreamReader 读取指定文件的内容。
try
{
// 创建 StreamReader 类的对象
StreamReader file = new StreamReader("test.txt");
String line;
// 从文件中读取内容
while ((line = file.ReadLine()) != null)
{
Console.WriteLine(line);
}
} catch (Exception e)
{
// 展示出错信息
Console.WriteLine("无法读取文件");
Console.WriteLine(e.Message);
}
Console.ReadKey();
StreamWriter 类继承自抽象类 TextWriter,用于以特定编码将字符写入文件。下表列出了 StreamWriter 类中一些常用的方法:
方法 | 描述 |
---|---|
public override void Close() | 关闭当前 StreamWriter 对象和基础流 |
public override void Flush() | 清理当前所有缓冲区,并将所有缓冲区数据写入基础流 |
public override void Write(char value) | 将一个字符写入文本流 |
public override void Write(string value) | 将一个字符串写入文本流 |
public virtual void Write(bool value) | 将布尔值的文本表示形式写入文本流 |
public virtual void Write(decimal value) | 将一个小数值的文本表示形式写入文本流 |
public virtual void Write(double value) | 将一个 8 字节浮点值的文本表示形式写入文本流 |
public virtual void Write(int value) | 将一个 4 字节有符号整数的文本表示形式写入文本流 |
public virtual void WriteLine() | 将行结束符写入文本流 |
举例:
使用 StreamReader 向文件中写入指定内容。
// 要写入文件中的数据
string[] str = new string[]{
"百度一下",
"https://www.baidu.com",
"C# 教程"
};
// 创建 StreamWriter 类的对象
StreamWriter file = new StreamWriter("test.txt");
// 将数组中的数据写入文件
foreach (string s in str)
{
file.WriteLine(s);
}
file.Close();
// 读取文件中的内容
string line;
StreamReader readfile = new StreamReader("test.txt");
while ((line = readfile.ReadLine()) != null)
{
Console.WriteLine(line);
}
readfile.Close();
Console.ReadKey();
System.IO 命名空间下的 BinaryReader 和 BinaryWriter 类可以用于二进制文件的读写。
BinaryReader 类用于从文件读取二进制数据,常用方法如下所示:
方法 | 描述 |
---|---|
public override void Close() | 关闭 BinaryReader 对象和基础流 |
public virtual int Read() | 从基础流中读取字符,并根据所使用的编码和从流中读取的特定字符,将流的当前位置前移 |
public virtual bool ReadBoolean() | 从当前流中读取一个布尔值,并将流的当前位置前移一个字节 |
public virtual byte ReadByte() | 从当前流中读取下一个字节,并将流的当前位置前移一个字节 |
public virtual byte[] ReadBytes(int count) | 从当前流中读取指定数目的字节到一个字节数组中,并将流的当前位置前移指定数目的字节 |
public virtual char ReadChar() | 从当前流中读取下一个字符,并把流的当前位置按照所使用的编码和从流中读取的指定的字符往前移 |
public virtual char[] ReadChars(int count) | 从当前流中读取指定数目的字符,并以字符数组的形式返回数据,并把流的当前位置按照所使用的编码和从流中读取的指定的字符往前移 |
public virtual string ReadString() | 从当前流中读取一个字符串,字符串以长度作为前缀,同时编码为一个七位的整数 |
public virtual int ReadInt32() | 从当前流中读取一个 4 字节有符号整数,并把流的当前位置前移四个字节 |
public virtual double ReadDouble() | 从当前流中读取一个 8 字节浮点值,并把流的当前位置前移八个字节 |
BinaryWriter 类用于向文件写入二进制数据,常用方法如下表所示:
方法 | 描述 |
---|---|
public override void Close() | 关闭 BinaryWriter 对象和基础流 |
public virtual void Flush() | 清理当前编写器的所有缓冲区,并将所有缓冲数据写入基础设备 |
public virtual long Seek(int offset,SeekOrigin origin) | 设置当前流中的位置 |
public virtual void Write(bool value) | 将一个字节的布尔值写入到当前流中,0 表示 false,1 表示 true |
public virtual void Write(byte value) | 将一个无符号字节写入到当前流中,并把流的位置前移一个字节 |
public virtual void Write(byte[] buffer) | 将一个字节数组写入到基础流中 |
public virtual void Write(char ch) | 将一个 Unicode 字符写入到当前流中,并把流的当前位置按照所使用的编码和要写入到流中的指定字符往前移 |
public virtual void Write(char[] chars) | 将一个字符数组写入到当前流中,并把流的当前位置按照所使用的编码和要写入到流中的指定字符往前移 |
public virtual void Write(string value) | 将一个有长度前缀的字符串按 BinaryWriter 的当前编码写如到流中,并把流的当前位置按照所使用的编码和要写入到流中的指定字符往前移 |
public virtual void Write(int value) | 将一个 4 字节有符号整数写入到当前流中,并把流位置前移四个字节 |
public virtual void Write(double value) | 将一个 8 字节浮点值写入到当前流中,并把流位置前移八个字节 |
举例:
下面通过示例演示二进制文件的读取和写入:
BinaryWriter bw;
BinaryReader br;
int i = 25;
double d = 3.14157;
bool b = true;
string s = "C#教程";
// 创建文件
try
{
bw = new BinaryWriter(new FileStream("mydata", FileMode.Create));
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n 文件创建失败!");
return;
}
// 写入文件
try
{
bw.Write(i);
bw.Write(d);
bw.Write(b);
bw.Write(s);
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n 文件写入失败!");
}
bw.Close();
// 读取文件
try
{
br = new BinaryReader(new FileStream("mydata", FileMode.Open));
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n 文件打开失败!");
return;
}
try
{
i = br.ReadInt32();
Console.WriteLine("Integer data: {0}", i);
d = br.ReadDouble();
Console.WriteLine("Double data: {0}", d);
b = br.ReadBoolean();
Console.WriteLine("Boolean data: {0}", b);
s = br.ReadString();
Console.WriteLine("String data: {0}", s);
}
catch (IOException e)
{
Console.WriteLine(e.Message + "\n 文件读取失败!.");
}
br.Close();
Console.ReadKey();
特性(英文名:Attribute)是一种用于在程序运行时传递各种元素(例如类、方法、结构、枚举等)行为信息的声明性代码。使用特性可以将元数据(例如编译器指令、注释、描述、方法和类等信息)添加到程序中。.Net Framework 提供了两种类型的特性,分别是预定义特性和自定义特性。
在 C# 中,特性具有以下属性:
C# 中定义特性的语法格式如下所示:
[attribute(positional_parameters, name_parameter = value, ...)]
其中,
[ ]
用来定义特性的名称和值;positional_parameters 用来指定基本信息;
name_parameter 用来指定可选信息;
.Net Framework 中提供了三个预定义特性。
用来描述如何使用自定义特性类。其语法格式如下:
[AttributeUsage (
validon,
AllowMultiple = allowmultiple,
Inherited = inherited
)]★参数说明如下:
- validon:用来定义特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All;
- allowmultiple:可选参数。为 AllowMultiple 属性指定一个布尔值,默认值为 false(单用的),如果为 true,则该特性是多用的;
- inherited:可选参数。为 Inherited 属性指定一个布尔值,默认为 false(不被继承),如果为 true,则该特性可被派生类继承;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method |AttributeTargets.Property, AllowMultiple = true)]
用来标记一个方法,它的执行依赖于指定的预处理标识符。根据该特性值的不同,在编译时会起到不同的效果。例如:当值为 Debug 或 Trace 时,会在调试代码时显示变量的值。预定义特性 Conditional 的语法格式如下:
[Conditional(
conditionalSymbol
)]
举例:预定义特性 Conditional 的使用:
#define DEBUG
using System;
using System.Diagnostics;
namespace MyFirstConsoleApp
{
class Program
{
static void Function1()
{
MyClass.Message("Function1 函数");
Function2();
}
static void Function2()
{
MyClass.Message("Function2 函数");
}
static void Main()
{
MyClass.Message("Main 函数!");
Function1();
Console.ReadKey();
}
}
public class MyClass
{
[Conditional("DEBUG")]
public static void Message(string msg)
{
Console.WriteLine(msg);
}
}
}
用来标记不应被使用的程序元素。例如:当我们需要使用一个新方法来替代类中的某个旧方法时,就可以使用该特性将旧方法标记为 obsolete(过时的)并输出一条消息,来提示我们应该使用新方法代替旧方法。预定义特性 Obsolete 的语法格式如下:
[Obsolete (
message,
iserror
)]语法说明如下:
- message:使用一个字符串,来描述项目为什么过时以及应该使用什么替代;
- iserror:默认值是 false(编译器会生成一个警告),如果为 true,那么编译器会把该项目当作一个错误;
举例:预定义特性 Obsolete 的使用:
using System;
namespace MyFirstConsoleApp
{
class Program
{
[Obsolete("OldMethod 已弃用,请改用 NewMethod", true)]
static void OldMethod()
{
Console.WriteLine("已弃用的函数");
}
static void NewMethod()
{
Console.WriteLine("新定义的函数");
}
static void Main(string[] args)
{
OldMethod();
}
}
}
自定义特性可以用于存储声明性的信息,还可以在运行时被检索。创建并使用自定义特性可以分为四个步骤:
自定义特性应该继承 System.Attribute 类,如下所示:
// 声明一个名为 DeBugInfo 的自定义特性
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DeBugInfo : System.Attribute
构建一个名为 DeBugInfo 的自定义特性,该特性可以存储下面列举的调试信息:
想定:
DeBugInfo 类中有三个用于存储前三个信息的私有属性和一个用于存储消息的公有属性。所以前三个属性是 DeBugInfo 类的必需的定位( positional)参数,而消息则是一个可选的命名(named)参数。每个特性都至少有一个构造函数,而且定位( positional)参数需要通过构造函数传递。
通过把特性放置在紧挨着它的目标上面来应用该特性,示例代码如下:
using System;
namespace MyFirstConsoleApp
{
class Program
{
static void Main()
{
Rectangle rec = new Rectangle(12, 15);
rec.Display();
Console.ReadKey();
}
}
[DebugInfo(45, "Zara Ali", "12/8/2012", Message = "返回值类型不匹配")]
[DebugInfo(49, "Nuha Ali", "10/10/2012", Message = "未使用变量")]
class Rectangle
{
// 成员变量
protected double length;
protected double width;
public Rectangle(double l, double w)
{
this.length = l;
this.width = w;
}
[DebugInfo(55, "Zara Ali", "19/10/2012", Message = "返回值类型不匹配")]
public double GetArea()
{
return length * width;
}
[DebugInfo(56, "Zara Ali", "19/10/2012")]
public void Display()
{
Console.WriteLine("Length: {0}", length);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", GetArea());
}
}
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DebugInfo : System.Attribute
{
private readonly int bugNo;
private readonly string developer;
private readonly string lastReview;
public string message;
public DebugInfo(int bg, string dev, string d)
{
this.bugNo = bg;
this.developer = dev;
this.lastReview = d;
}
public int BugNo
{
get
{
return bugNo;
}
}
public string Developer
{
get
{
return developer;
}
}
public string LastReview
{
get
{
return lastReview;
}
}
public string Message
{
get
{
return message;
}
set
{
message = value;
}
}
}
}
反射(英文名:Reflection)是指程序可以访问、检测和修改它本身状态或行为的一种能力。反射中提供了用来描述程序集、模块和类型的对象,可以使用反射动态地创建类型的实例,并将类型绑定到现有对象,或者从现有对象中获取类型,然后调用其方法或访问其字段和属性。如果代码中使用了特性,也可以利用反射来访问它们。
C# 中反射具有以下用途:
前面我们提到了可以使用反射查看特性的信息,下面就来看一下具体的操作步骤。首先需要初始化 System.Reflection 类的 MemberInfo 对象,用来发现与类关联的属性,例如:
System.Reflection.MemberInfo info = typeof(MyClass);
举例:
using System;
using System.Reflection;
namespace MyFirstConsoleApp
{
class Program
{
static void Main()
{
Rectangle rec = new Rectangle(4.5, 7.5);
rec.Display();
Type type = typeof(Rectangle);
// 遍历 Rectangle 类的属性
foreach (object attributes in type.GetCustomAttributes(false))
{
DebugInfo dbi = (DebugInfo)attributes;
if (null != dbi)
{
Console.WriteLine("Bug 编号: {0}", dbi.BugNo);
Console.WriteLine("开发者: {0}", dbi.Developer);
Console.WriteLine("上次审核时间: {0}", dbi.LastReview);
Console.WriteLine("评论: {0}", dbi.Message);
}
}
// 遍历函数属性
foreach (MethodInfo m in type.GetMethods())
{
foreach (object attributes in m.GetCustomAttributes(true))
{
if (attributes is DebugInfo dbi)
{
Console.WriteLine("Bug 编号: {0}, 函数名: {1}", dbi.BugNo, m.Name);
Console.WriteLine("开发者: {0}", dbi.Developer);
Console.WriteLine("上次审核时间: {0}", dbi.LastReview);
Console.WriteLine("评论: {0}", dbi.Message);
}
}
}
Console.ReadKey();
}
}
[DebugInfo(45, "Zara Ali", "12/8/2012", Message = "返回值类型不匹配")]
[DebugInfo(49, "Nuha Ali", "10/10/2012", Message = "未使用变量")]
class Rectangle
{
// 成员变量
protected double length;
protected double width;
public Rectangle(double l, double w)
{
this.length = l;
this.width = w;
}
[DebugInfo(55, "Zara Ali", "19/10/2012", Message = "返回值类型不匹配")]
public double GetArea()
{
return length * width;
}
[DebugInfo(56, "Zara Ali", "19/10/2012", Message = "返回值类型不匹配")]
public void Display()
{
Console.WriteLine("Length: {0}", length);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", GetArea());
}
}
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DebugInfo : System.Attribute
{
private readonly int bugNo;
private readonly string developer;
private readonly string lastReview;
public string message;
public DebugInfo(int bg, string dev, string d)
{
this.bugNo = bg;
this.developer = dev;
this.lastReview = d;
}
public int BugNo
{
get
{
return bugNo;
}
}
public string Developer
{
get
{
return developer;
}
}
public string LastReview
{
get
{
return lastReview;
}
}
public string Message
{
get
{
return message;
}
set
{
message = value;
}
}
}
}
属性(英文名:Property)是类、结构体、接口的成员。类或结构体中的成员变量称之为字段,属性是字段的扩展。根据面向对象语言的封装思想,字段最好设为private。因为这样可以防止客户端直接对字段进行篡改,从而保证了内部成员的完整性。
为了访问类中的私有字段,且让字段的访问过程更加简单,C# 提出了属性的概念。即,通过操作属性来访问字段,从而避免了方法的调用。属性除了能直接访问私有字段外,还可以根据需要加入更多的逻辑控制代码。
属性定义主要由get访问器和set访问器组成。get访问器负责对字段值进行读取,set访问器负责对字段进行赋值。get访问器和set访问器可以理解为两个方法:一个用来返回字段的值,一个用来把用户传入的值赋给字段。在声明属性访问器时可以仅声明其中一个,也可以两个同时声明。
举例:
using System;
namespace MyFirstConsoleApp
{
class Program
{
public static void Main()
{
// 创建一个新的 Student 对象
Student s = new Student
{
// 设置 student 的 code、name 和 age
Code = "001",
Name = "Zara",
Age = 9
};
Console.WriteLine("学生信息: {0}", s);
// 增加年龄
s.Age += 1;
Console.WriteLine("学生信息: {0}", s);
Console.ReadKey();
}
}
public class Student
{
// 定义私有字段
private string code;
private string name;
private int age;
//
// 定义公有属性
//
// 声明类型为 string 的 Code 属性
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// 声明类型为 string 的 Name 属性
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// 声明类型为 int 的 Age 属性
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public override string ToString()
{
return "编号 = " + code + ", 姓名 = " + name + ", 年龄 = " + age;
}
}
}
抽象类中可以拥有抽象属性,这些属性必须在派生类中实现。如:
using System;
namespace MyFirstConsoleApp
{
class Program
{
public static void Main()
{
// 创建一个新的 Student 对象
Student s = new Student
{
// 设置 student 的 code、name 和 age
Code = "001",
Name = "Zara",
Age = 9
};
Console.WriteLine("学生信息: {0}", s);
// 增加年龄
s.Age += 1;
Console.WriteLine("学生信息: {0}", s);
Console.ReadKey();
}
}
public abstract class Person
{
public abstract string Name
{
get;
set;
}
public abstract int Age
{
get;
set;
}
}
public class Student : Person
{
private string code;
private string name;
private int age;
// 声明类型为 string 的 Code 属性
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// 重写基类的 Name 属性
public override string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// 重写基类的 Age 属性
public override int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public override string ToString()
{
return "编号 = " + code + ", 姓名 = " + name + ", 年龄 = " + age;
}
}
}
索引器(英文名:Indexer)是类中的一个特殊成员,它能够以类似数组的形式来操作对象,使程序看起来更为直观,更容易编写。索引器与属性类似,在定义索引器时同样会用到 get 和 set 访问器,不同的是,访问属性不需要提供参数而访问索引器则需要提供相应的参数。
C# 中索引器使用 this 关键字来定义,语法格式如下:
索引器类型 this[int index]
{
// get 访问器
get
{
// 返回指定 index 的值
}
// set 访问器
set
{
// 设置指定 index 的值
}
}备注:索引器中的索引不必是整数,也可以是其他类型,例如字符串类型。
索引器可以被重载。在声明索引器时也可以带有多个参数,每个参数可以是不同的类型。
举例:
using System;
using System.Collections;
namespace MyFirstConsoleApp
{
class Program
{
public static void Main()
{
EmployeeInfoIndexerClass indexer = new EmployeeInfoIndexerClass();
indexer["张三", "技术部"] = 1001;
indexer["李四", "人事部"] = 1002;
Console.WriteLine("张三的工号:" + indexer["张三", "技术部"]);
Console.WriteLine("李四的工号:" + indexer["李四", "人事部"]);
EmployeeInfo ei = indexer[1001];
if (ei != null)
{
Console.WriteLine("工号1001的姓名: " + ei.EmployeeName);
Console.WriteLine("工号1001的部门: " + ei.Department);
}
Console.ReadKey();
}
}
///
/// 员工类
///
public class EmployeeInfo
{
// 工号
public int EmployeeNumber { get; set; }
// 姓名
public string EmployeeName { get; set; }
// 部门
public string Department { get; set; }
///
/// 有参构造函数
///
/// 工号
/// 姓名
/// 部门
public EmployeeInfo(int employeeNumber, string employeeName, string department)
{
EmployeeNumber = employeeNumber;
EmployeeName = employeeName;
Department = department;
}
}
public class EmployeeInfoIndexerClass
{
private readonly ArrayList arrayList;
public EmployeeInfoIndexerClass()
{
this.arrayList = new ArrayList();
}
// 声明一个索引器:通过姓名及部门查找工号。
public int this[string employeeName, string department]
{
get
{
foreach (EmployeeInfo ei in arrayList)
{
if (ei.EmployeeName.Equals(employeeName) && ei.Department.Equals(department))
{
return ei.EmployeeNumber;
}
}
return -1;
}
set
{
arrayList.Add(new EmployeeInfo(value, employeeName, department));
}
}
// 声明一个索引器:通过工号查找姓名和部门。
public EmployeeInfo this[int employeeNumber]
{
get
{
foreach (EmployeeInfo ei in arrayList)
{
if (ei.EmployeeNumber == employeeNumber)
{
return ei;
}
}
return null;
}
}
}
}
C# 中的委托(英文名:Delegate)类似于 C 或 C++ 中的函数指针,是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。委托特别适用于实现事件和回调方法,所有的委托都派生自 System.Delegate 类。在实例化委托时,可以将委托的实例与具有相同返回值类型的方法相关联,这样就可以通过委托来调用方法。另外,使用委托还可以将方法作为参数传递给其他方法。
声明委托需要使用 delegate 关键字,语法格式如下:
[访问修饰符] delegate
delegate-name( ) 其中:
return type 为返回值类型;
delegate-name 为委托名称;
parameter list 为参数列表;
委托可以引用与委托具有相同签名的方法。也就是说,委托在声明时即确定了委托可以引用的方法。
委托一旦声明,想要使用就必须使用 new 关键字来创建委托的实例,同时将其与特定的方法关联。如下所示:
using System;
namespace MyFirstConsoleApp
{
///
/// 定义委托类型(委托是类型,与Class同级,所以可以定义在类外)
/// 委托也可以定义在类中
///
public delegate double Calculate(double x, double y);
class Program
{
public static double Add(double x, double y)
{
return x + y;
}
public static double Dec(double x, double y)
{
return x - y;
}
public static void Main()
{
// 创建委托实例
Calculate calAdd = new Calculate(Add);
// 使用委托对象调用方法
double resultAdd = calAdd(6, 8);
Console.WriteLine($"Add计算的结果为: {resultAdd}");
Calculate calDec = new Calculate(Dec);
double resultDec = calDec(6, 8);
Console.WriteLine($"Dec计算的结果为: {resultDec}");
Console.ReadKey();
}
}
}
委托对象有一个非常有用的属性,那就是可以通过使用 + 运算符将多个对象分配给一个委托实例,还可以使用 - 运算符从委托中移除已分配的对象。当委托被调用时会依次调用列表中的委托实例,委托的这个属性被称为委托的多播,也可称为组播。利用委托的这个属性,我们可以创建一个调用委托时要调用的方法列表。(注意:仅可合并类型相同的委托。)
举例:
using System;
namespace MyFirstConsoleApp
{
class Program
{
delegate int NumberChange(int n);
private static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static void Main()
{
// 创建委托实例
NumberChange nc;
NumberChange ncAdd = new NumberChange(AddNum);
NumberChange ncMul = new NumberChange(MultNum);
nc = ncAdd;
nc += ncMul;
// 调用多播
double result = nc(6);
Console.WriteLine($"计算的结果为: {result}"); // 96
Console.ReadKey();
}
}
}
委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。我们使用这个委托来调用两个方法,第一个把字符串打印到控制台,第二个把字符串打印到文件。
using System;
using System.IO;
namespace MyFirstConsoleApp
{
class Program
{
// 委托的声明
public delegate void PrintString(string s);
// 该方法打印到控制台
public static void WriteToScreen(string str)
{
Console.WriteLine($"打印到控制台的字符串: {str}");
}
// 该方法打印到文件
public static void WriteToFile(string str)
{
FileStream fs = new FileStream("./message.txt", FileMode.Append, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine(str);
sw.Flush();
sw.Close();
fs.Close();
}
// 该方法把委托作为参数,并使用它调用方法
public static void SendString(PrintString printString)
{
printString("Hello World!");
}
public static void Main()
{
// 创建委托实例
// PrintString ps = new PrintString(WriteToFile);
// 还可以这么写
PrintString ps = WriteToFile;
SendString(ps);
// 可以直接把方法名作为参数传入
SendString(WriteToScreen);
Console.ReadKey();
}
}
}
举例2: