预处理命令

预处理器指令指导编译器在实际编译开始之前对信息进行预处理。

所有的预处理器指令都是以 # 开始。且在一行上,只有空白字符可以出现在预处理器指令之前。

预处理器指令不是语句,所以它们不以分号;结束。

C# 编译器没有一个单独的预处理器,但是,指令被处理时就像是有一个单独的预处理器一样。 在 C# 中,预处理器指令用于在条件编译中起作用。与 C 和 C++ 不同的是,它们不是用来创建宏。 一个预处理器指令必须是该行上的唯一指令。

(这种预处理命令应该摒弃掉)

一、指令列表

预处理器指令 描述
#define 它用于定义一系列成为符号的字符。
#undef 它用于取消定义符号。
#if 它用于测试符号是否为真。
#else 它用于创建复合条件指令,与 #if 一起使用。
#elif 它用于创建复合条件指令。
#endif 指定一个条件指令的结束。
#line 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
#error 它允许从代码的指定位置生成一个错误。
#warning 它允许从代码的指定位置生成一级警告。
#region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
#endregion 它标识着 #region 块的结束。

二、#define 预处理器

#define 预处理器指令创建符号常量。 #define 允许您定义一个符号,这样,通过使用符号作为传递给 #if 指令的表达式,表达式将返回 true。它的语法如下:

#define symbol
  • DefineDemo.cs
#define PI

using System;

namespace cshape_test
{
	public class DefineDemo
	{
		//PI has been defined!
		static void Main(string[] args)
		{
			#if (PI)
				Console.WriteLine("PI has been defined!");
			#else
				Console.WriteLine("PI has not been defined!");
			#endif
		}
	}
}

三、条件指令

您可以使用 #if 指令来创建一个条件指令。条件指令用于测试符号是否为真。如果为真,编译器会执行 #if 和下一个指令之间的代码。

条件指令的语法:

#if symbol [operator symbol]...

其中,symbol 是要测试的符号名称。您也可以使用 true 和 false,或在符号前放置否定运算符。

运算符符号是用于评价符号的运算符。可以运算符可以是下列运算符之一:

  • == (equality)

  • != (inequality)

  • && (and)

  • || (or)

您也可以用括号把符号和运算符进行分组。条件指令用于在调试版本或编译指定配置时编译代码。 一个以 #if 指令开始的条件指令,必须显示地以一个 #endif 指令终止。

下面的程序演示了条件指令的用法:

  • ConditionOrder()
#define NIGHT
#define VOICE

using System;

namespace cshape_test
{
	
	public class DefineDemo
	{
		//night and voice defined!
		static void ConditionOrder()
		{
			#if (NIGHT && VOICE)
				Console.WriteLine("night and voice defined!");
			#elif(!NIGHT && !VOICE)
				Console.WriteLine("night and voice not defined!");
			#endif
				
		}
	}
}

正则表达式

正则表达式 是一种匹配输入文本的模式。.Net 框架提供了允许这种匹配的正则表达式引擎。 模式由一个或多个字符、运算符和结构组成。

(正则表达式很多语言都支持, 并不是 C# 的专属。平时使用到再看也可。暂时跳过。)

一、定义正则表达式

下面列出了用于定义正则表达式的各种类别的字符、运算符和结构。

  • 字符转义

  • 字符类

  • 定位点

  • 分组构造

  • 限定符

  • 反向引用构造

  • 备用构造

  • 替换

  • 杂项构造

二、Regex 实例

  • RegexDemo.cs
using System;
using System.Text.RegularExpressions;

namespace cshape_test
{
	public class RegexDemo
	{
		//显示所有已S开头的单词
		static void ShowStartWithS(String text)
		{
			string regex = @"\bs\S{0,}";

			MatchCollection mc = Regex.Matches(text, regex);

			foreach (Match match in mc)
			{
				Console.WriteLine("match: {0}", match);
			}
		}

		//match: start
		//match: spring
		static void Main(string[] agrs)
		{
			String str = "hello world start from spring";
			ShowStartWithS(str);
		}
	}
}

异常处理

异常是在程序执行期间出现的问题。C# 中的异常是对程序运行时出现的特殊情况的一种响应,比如尝试除以零。

异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。C# 异常处理时建立在四个关键词之上的:try、catch、finally 和 throw。

  • try

一个 try 块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。

  • catch

程序通过异常处理程序捕获异常。catch 关键字表示异常的捕获。

  • finally

finally 块用于执行给定的语句,不管异常是否被抛出都会执行。例如,如果您打开一个文件,不管是否出现异常文件都要被关闭。

  • throw

当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。

一、语法

假设一个块将出现异常,一个方法使用 try 和 catch 关键字捕获异常。try/catch 块内的代码为受保护的代码, 使用 try/catch 语法如下所示:

try
{
   // 引起异常的语句
}
catch( ExceptionName e1 )
{
   // 错误处理代码
}
catch( ExceptionName e2 )
{
   // 错误处理代码
}
catch( ExceptionName eN )
{
   // 错误处理代码
}
finally
{
   // 要执行的语句
}

二、C# 中的异常类

C# 异常是使用类来表示的。C# 中的异常类主要是直接或间接地派生于 System.Exception 类。 System.ApplicationException 和 System.SystemException 类是派生于 System.Exception 类的异常类。

System.ApplicationException 类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。

System.SystemException 类是所有预定义的系统异常的基类。

下表列出了一些派生自 Sytem.SystemException 类的预定义的异常类:

异常类 描述
System.IO.IOException 处理 I/O 错误。
System.IndexOutOfRangeException 处理当方法指向超出范围的数组索引时生成的错误。
System.ArrayTypeMismatchException 处理当数组类型不匹配时生成的错误。
System.NullReferenceException 处理当依从一个空对象时生成的错误。
System.DivideByZeroException 处理当除以零时生成的错误。
System.InvalidCastException 处理在类型转换期间生成的错误。
System.OutOfMemoryException 处理空闲内存不足生成的错误。
System.StackOverflowException 处理栈溢出生成的错误。

三、异常处理

C# 以 try 和 catch 块的形式提供了一种结构化的异常处理方案。使用这些块,把核心程序语句与错误处理语句分离开。

这些错误处理块是使用 try、catch 和 finally 关键字实现的。下面是一个当除以零时抛出异常的实例:

  • ExceptionHandleDemo.cs
public class ExceptionHandleDemo

    static void Divide(int dividend, int divisor)
    {
        int result = 0;

        try
        {
            result = dividend / divisor;
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Exception caught: {0}", ex);
        }
        finally 
        { 
            Console.WriteLine("Result: {0}", result);
        }
    }

//	Exception caught: System.DivideByZeroException: Attempted to divide by zero.
// at cshape_test.ExceptionHandleDemo.divide(System.Int32 dividend, System.Int32 divisor) [0x00004] in /Users/houbinbin/Projects/cshape_test/cshape_test/ExceptionHandleDemo.cs:17 
//Result: 0
    static void Main(string[] args)
    {
        Divide(10, 0);
    }
}

四、创建用户自定义异常

您也可以定义自己的异常。用户自定义的异常类是派生自 ApplicationException 类。 (C#中没有检查异常和运行时异常之别)

下面的实例演示了这点:

  • MyException.cs
using System;
namespace cshape_test
{
	class MyException : ApplicationException
	{ 
		public MyException()
		{
		}

		public MyException(string message) : base(message)
		{
		}
	}


	public class ExceptionHandleDemo
	{
		static int MyExceptionTest(int dividend, int divisor)
		{
			if (0 == divisor)
			{
				throw new MyException("divisor can not be zero!");
			}

			return dividend / divisor;
		}

 //		Exception caught: cshape_test.MyException: divisor can not be zero!
 // 	at cshape_test.ExceptionHandleDemo.MyExceptionTest(System.Int32 dividend, System.Int32 divisor) [0x0000d] in /Users/houbinbin/Projects/cshape_test/cshape_test/ExceptionHandleDemo.cs:27 
 // 	at cshape_test.ExceptionHandleDemo.Main(System.String[] args) [0x00005] in /Users/houbinbin/Projects/cshape_test/cshape_test/ExceptionHandleDemo.cs:60 
		static void Main(string[] args)
		{
			try
			{
				int result = MyExceptionTest(10, 0);
			}
			catch (MyException ex)
			{
				Console.WriteLine("Exception caught: {0}", ex);
			}
		}
	}
}

五、抛出对象

如果异常是直接或间接派生自 System.Exception 类,您可以抛出一个对象。 您可以在 catch 块中使用 throw 语句来抛出当前的对象,如下所示:

Catch(Exception e)
{
   //...
   Throw e
}

文件的输入与输出

一个 文件 是一个存储在磁盘中带有指定名称和目录路径的数据集合。当打开文件进行读写时,它变成一个 。 从根本上说,流是通过通信路径传递的字节序列。有两个主要的流:输入流 和 输出流。

输入流用于从文件读取数据(读操作)。

输出流用于向文件写入数据(写操作)。

(这类的知识比较多, 建议使用时学习)

一、I/O 类

System.IO 命名空间有各种不同的类,用于执行各种文件操作,如创建和删除文件、读取或写入文件,关闭文件等。

下表列出了一些 System.IO 命名空间中常用的非抽象类:

I/O 类 描述
BinaryReader 从二进制流读取原始数据。
BinaryWriter 以二进制格式写入原始数据。
BufferedStream 字节流的临时存储。
Directory 有助于操作目录结构。
DirectoryInfo 用于对目录执行操作。
DriveInfo 提供驱动器的信息。
File 有助于处理文件。
FileInfo 用于对文件执行操作。
FileStream 用于文件中任何位置的读写。
MemoryStream 用于随机访问存储在内存中的数据流。
Path 对路径信息执行操作。
StreamReader 用于从字节流中读取字符。
StreamWriter 用于向一个流中写入字符。
StringReader 用于读取字符串缓冲区。
StringWriter 用于写入字符串缓冲区。

FileStream 类

System.IO 命名空间中的 FileStream 类有助于文件的读写与关闭。该类派生自抽象类 Stream。

您需要创建一个 FileStream 对象来创建一个新的文件,或打开一个已有的文件。创建 FileStream 对象的语法如下:

FileStream <object_name> = new FileStream( <file_name>,
<FileMode Enumerator>, <FileAccess Enumerator>, <FileShare Enumerator>);

(可见需要指定的参数比较多,所以可以编写点默认的文件工具类。应该有默认的)

1、FileMode

枚举定义了各种打开文件的方法。FileMode 枚举的成员有:

  • Append:打开一个已有的文件,并将光标放置在文件的末尾。如果文件不存在,则创建文件。

  • Create:创建一个新的文件。如果文件已存在,则删除旧文件,然后创建新文件。

  • CreateNew:指定操作系统应创建一个新的文件。如果文件已存在,则抛出异常。

  • Open:打开一个已有的文件。如果文件不存在,则抛出异常。

  • OpenOrCreate:指定操作系统应打开一个已有的文件。如果文件不存在,则用指定的名称创建一个新的文件打开。

  • Truncate:打开一个已有的文件,文件一旦打开,就将被截断为零字节大小。 然后我们可以向文件写入全新的数据,但是保留文件的初始创建日期。如果文件不存在,则抛出异常。

2、FileAccess

枚举的成员有:Read、ReadWrite 和 Write。

3、FileShare

FileShare 枚举的成员有:

  • Inheritable:允许文件句柄可由子进程继承。Win32 不直接支持此功能。

  • None:谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。

  • Read:允许随后打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。

  • ReadWrite:允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。

  • Write:允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。 但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。

  • Delete:允许随后删除文件。

FileStreamDemo.cs

using System;
using System.IO;

namespace cshape_test
{
	public class FileStreamDemo
	{
		/// <summary>
		/// Reads the content of the file.
		/// </summary>
		/// <param name="filePath">File path.</param>
		static void ReadFileContent(String filePath)
		{
			FileStream stream = new FileStream(filePath,
			FileMode.OpenOrCreate, FileAccess.ReadWrite);

			for (int i = 1; i <= 20; i++)
			{
				stream.WriteByte((byte)i);
			}

			stream.Position = 0;

			for (int i = 0; i <= 20; i++)
			{
				Console.Write(stream.ReadByte() + " ");
			}
			stream.Close();
		}

		//1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 108 
		static void Main(string[] args)
		{
			string filePath = "/Users/houbinbin/mail.html";
			ReadFileContent(filePath);		
		}
	}
}

二、文本文件的读写

它涉及到文本文件的读写。StreamReader 和 StreamWriter 类有助于完成文本文件的读写。 这些类从抽象基类 Stream 继承,Stream 支持文件流的字节读写。

1、StreamReader 类

StreamReader 类继承自抽象基类 TextReader,表示阅读器读取一系列字符。

/// <summary>
/// Reads the first line by stream reader.
/// </summary>
/// <param name="filePath">File path.</param>
static void ReadFirstLineByStreamReader(string filePath)
{
    try
    {
        // 创建一个 StreamReader 的实例来读取文件 
        // using 语句也能关闭 StreamReader
        using (StreamReader sr = new StreamReader(filePath))
        {
            string firstLine = sr.ReadLine().Trim();
            Console.WriteLine("first line of file {0} is: {1}", filePath, firstLine);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Read file of {0} has exception: {1}", filePath, ex);
    }
}

//first line of file /Users/houbinbin/ali.sh is: #!/bin/sh
static void Main(string[] args)
{
    string filePath = "/Users/houbinbin/ali.sh";
    ReadFirstLineByStreamReader(filePath);
}

2、StreamWriter 类

/// <summary>
/// Writes the file by stream writer.
/// </summary>
/// <param name="filePath">File path.</param>
/// <param name="content">Content to write</param>
static void WriteByStreamWriter(string filePath, string content)
{
    try
    {
        using (StreamWriter sw = new StreamWriter(filePath))
        {
            sw.WriteLine(content);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Write file of {0} has exception: {1}", filePath, ex);
    }
}

static void Main(string[] args)
{
    string filePath = "/Users/houbinbin/WriteByStreamWriter.txt";
    string content = "hello WriteByStreamWriter";
    WriteByStreamWriter(filePath, content);
}

三、二进制文件的读写

它涉及到二进制文件的读写。BinaryReader 和 BinaryWriter 类有助于完成二进制文件的读写。

  • BinaryDemo.cs
using System;
using System.IO;

namespace cshape_test
{
	public class BinaryRWDemo
	{
		private const string PATH = "/Users/houbinbin/binary.txt";

		static void BinaryWrite()
		{
			int i = 20;
			double d = 12.5;
			string str = "hello";

			FileStream fileStream = null;
			try
			{
				fileStream = new FileStream(PATH, FileMode.Create);
				BinaryWriter bw = new BinaryWriter(fileStream);
				bw.Write(i);
				bw.Write(d);
				bw.Write(str);
			}
			catch (IOException ex)
			{
				Console.WriteLine("Write of file {0} has meet exception :{1}", PATH, ex);	
			}
			finally
			{
				if (fileStream != null)
				{
					fileStream.Close();
				}
			}
		}

		

		//Read int is: 20
		//Read double is: 12.5
		//Read string is: hello
		static void Main(string[] args)
		{
			BinaryWrite();
		}
	}
}

直接用 TEXTLINE 打开文件内容为:

1400 0000 0000 0000 0000 2940 0568 656c
6c6f
  • BinaryRead()
//Read int is: 20
//Read double is: 12.5
//Read string is: hello
static void BinaryRead()
{
    FileStream fileStream = null;
    try
    {
        fileStream = new FileStream(PATH, FileMode.Open);
        BinaryReader br = new BinaryReader(fileStream);

        int i = br.ReadInt32();
        Console.WriteLine("Read int is: {0}", i);

        double d = br.ReadDouble();
        Console.WriteLine("Read double is: {0}", d);

        string str = br.ReadString();
        Console.WriteLine("Read string is: {0}", str);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Read of file {0} has meet exception :{1}", PATH, ex);
    }
    finally 
    {
        if (null != fileStream)
        {
            fileStream.Close();
        }	
    }

}

四、Windows 文件系统的操作

它让 C# 程序员能够浏览并定位 Windows 文件和目录。

using System;
using System.IO;

namespace cshape_test
{
	public class FileInfoDemo
	{

		/// <summary>
		/// Shows the dir info.
		/// </summary>
		/// <param name="dirPath">Dir path.</param>
		static void ShowDirInfo(string dirPath)
		{
			DirectoryInfo dir = new DirectoryInfo(dirPath);
			FileInfo[] files = dir.GetFiles();

			foreach (FileInfo file in files)
			{
				Console.WriteLine("Name: {0}, Size: {1}", file.FullName, file.Length);
			}
		}

		//Name: /Users/houbinbin/logs/.DS_Store, Size: 6148
		//Name: /Users/houbinbin/logs/test-log4j.log, Size: 0
		static void Main(string[] args)
		{
			string dirPath = "/Users/houbinbin/logs";
			ShowDirInfo(dirPath);
		}

	}
}