在这条成长的路上,有一些核心概念将成为你开发过程中的得力助手---命名空间、预处理指令、正则表达式、异常处理和文件输入输出,这些看似独立的技术,实际上在大多数应用中都紧密相连,共同构成了C#开发的基础。
目录
C#命名空间
在C#当中命名空间是一个非常重要的概念,它用于组织代码中的类、接口、结构、枚举等类型,通过命名空间可以避免命名冲突并使得代码更加清晰,结构化易于维护,简单来说命名空间就像是一个容器,将相关的代码元素组合在一起以便在较大的项目中进行管理和访问。
我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名,如下是命名空间的具体作用:
1)避免命名冲突:通过将相似的类或其他类型放入不同的命名空间中避免了命名冲突
2)代码组织可读:帮助将相关的代码逻辑分组从而提高代码的可读性和可维护性
3)简化代码引用:只需要引用命名空间而不必担心类名冲突,特别是当使用大量库或框架时
命名空间:以关键字namespace开始后跟命名空间的名称,Logger类被放在了MyApp.Utilities命名空间中这意味着它属于该命名空间的一部分,如下所示:
cs
namespace MyApp.Utilities
{
public class Logger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
}
使用命名:在不同的代码文件中使用命名空间时,我们可以使用using关键字来引用某个命名空间从而避免每次都写完整的命名空间路径,例如:
cs
using MyApp.Utilities;
class Program
{
static void Main()
{
Logger logger = new Logger();
logger.Log("Hello, World!");
}
}
命名嵌套:C#支持命名空间的嵌套,这意味着可以在一个命名空间内部定义另一个命名空间,这样可以进一步帮助细化代码结构,举个例子:
cs
namespace MyApp
{
namespace Utilities
{
public class Logger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
}
namespace Models
{
public class User
{
public string Name { get; set; }
}
}
}
注意: 命名空间应该反映其包含类型的功能所有应该使用有意义的命名;虽然命名空间可以嵌套但过度嵌套可能导致代码结构复杂难以理解应避免过多的嵌套层次;命名空间应与项目目录结构保持一致这样可以使得项目文件和代码保持一致性。
C#预处理指令
在C#中预处理指令是一种在编译阶段处理源代码的指令,主要用于控制编译器如何处理代码,这些指令在编译时起作用而不是在运行时,因此它们并不直接影响程序的逻辑而是提供了编译时的控制和灵活性,预处理指令通常用于条件编译、宏定义、代码优化等。下表列出了C#中可用的预处理器指令:
指令 | 描述 |
---|---|
#define |
定义一个符号,可以用于条件编译。 |
#undef |
取消定义一个符号。 |
#if |
开始一个条件编译块,如果符号被定义则包含代码块。 |
#elif |
如果前面的 #if 或 #elif 条件不满足,且当前条件满足,则包含代码块。 |
#else |
如果前面的 #if 或 #elif 条件不满足,则包含代码块。 |
#endif |
结束一个条件编译块。 |
#warning |
生成编译器警告信息。 |
#error |
生成编译器错误信息。 |
#region |
标记一段代码区域,可以在IDE中折叠和展开这段代码,便于代码的组织和阅读。 |
#endregion |
结束一个代码区域。 |
#line |
更改编译器输出中的行号和文件名,可以用于调试或生成工具的代码。 |
#pragma |
用于给编译器发送特殊指令,例如禁用或恢复特定的警告。 |
#nullable |
控制可空性上下文和注释,允许启用或禁用对可空引用类型的编译器检查。 |
#define和#undef:在下面的例子中,#define DEBUG 定义了一个名为DEBUG的符号,代码中可以使用#if DEBUG来判断这个符号是否已经定义。
cs
#define DEBUG // 定义DEBUG符号
using System;
class Program
{
static void Main()
{
#if DEBUG
Console.WriteLine("Debug mode is enabled");
#endif
}
}
#if, #else, #elif, #endif:在下面的代码中,如果定义了DEBUG,则输出"Debugging mode enabled."否则输出 "Production mode."。
cs
#define DEBUG
using System;
class Program
{
static void Main()
{
#if DEBUG
Console.WriteLine("Debugging mode enabled.");
#else
Console.WriteLine("Production mode.");
#endif
}
}
#warning和#error:在下面这个例子中,#warning会在没有定义FEATURE_X时发出警告,而#error会在没有定义FEATURE_Y时导致编译错误。
cs
#define FEATURE_X
using System;
class Program
{
static void Main()
{
#if !FEATURE_X
#warning "Feature X is not enabled. Some functionality may not work."
#endif
#if !FEATURE_Y
#error "Feature Y is required but not defined."
#endif
}
}
#region和#endregion:用于将代码块包裹起来,使其可折叠帮助开发者整理和管理代码。
cs
#region MyRegion
public void MyMethod()
{
Console.WriteLine("Hello, World!");
}
#endregion
使用预处理器指令的注意事项:
1)提高代码可读性:使用#region可以帮助分隔代码块,提高代码的组织性。
2)条件编译:通过#if等指令可以在开发和生产环境中编译不同的代码,方便调试和发布。
3)警告和错误:通过#warning和#error可以在编译时提示开发人员注意特定问题。
C#正则表达式
在C#中正则表达式是一种强大的文本处理工具,用于查找、匹配、替换、验证和操作字符串,通过定义特定的模式来描述字符的匹配规则且这种模式可以被用于各种文本操作,如字符串搜索、数据提取和格式化等。
基本构成:正则表达式由各种字符、符号和元字符组成,常见的正则表达式元素包括:
类型 | 字符 | 匹配 |
---|---|---|
基本字符 | a、b、c | 匹配字符 a、b、c |
基本字符 | abc | 匹配字符串 "abc" |
元字符 | . | 匹配任意单个字符(除换行符) |
元字符 | ^ | 表示匹配字符串的开头 |
元字符 | $ | 表示匹配字符串的结尾 |
元字符 | [] | 表示字符集合,匹配其中的任意一个字符 |
元字符 | | | 表示"或"操作,匹配左边或右边的内容 |
字符类别 | \d | 匹配任何数字,相当于 [0-9] |
字符类别 | \D | 匹配任何非数字字符 |
字符类别 | \w | 匹配任何字母、数字或下划线,相当于 [A-Za-z0-9_] |
字符类别 | \W | 匹配任何非字母、非数字、非下划线的字符 |
字符类别 | \s | 匹配任何空白字符(空格、制表符、换行符等) |
字符类别 | \S | 匹配任何非空白字符 |
量词 | * | 匹配前面的表达式零次或多次 |
量词 | + | 匹配前面的表达式一次或多次 |
量词 | ? | 匹配前面的表达式零次或一次 |
量词 | {n} | 匹配前面的表达式恰好 n 次 |
量词 | {n,} | 匹配前面的表达式至少 n 次 |
量词 | {n,m} | 匹配前面的表达式至少 n 次,但不超过 m 次 |
分组和捕获 | () | 将表达式括起来表示一个分组 |
分组和捕获 | (?:...) | 非捕获分组,表示分组但不捕获其内容 |
分组和捕获 | \1, \2, \3... | 表示第1、2、3个捕获组的内容 |
转义字符 | \ | 用于转义元字符,使其失去特殊意义,例如 \. 匹配点字符 |
常见示例:
javascript
// 匹配邮箱地址:匹配有效的电子邮件地址
string pattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
// 匹配电话号码:匹配如 (123) 456-7890 格式的电话号码
string pattern = @"\(\d{3}\) \d{3}-\d{4}";
// 验证日期格式:匹配日期格式为 2024-12-07 的字符串
string pattern = @"^\d{4}-\d{2}-\d{2}$";
在下面这个例子中,使用正则表达式分别查找文本中的电子邮件地址和电话号码,如果找到了匹配项就输出匹配的内容:
cs
using System;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string input = "My email is john.doe@example.com, and my phone number is (123) 456-7890.";
// 正则表达式匹配电子邮件地址
string emailPattern = @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}";
Match emailMatch = Regex.Match(input, emailPattern);
if (emailMatch.Success)
{
Console.WriteLine("Email found: " + emailMatch.Value);
}
// 正则表达式匹配电话号码
string phonePattern = @"\(\d{3}\) \d{3}-\d{4}";
Match phoneMatch = Regex.Match(input, phonePattern);
if (phoneMatch.Success)
{
Console.WriteLine("Phone number found: " + phoneMatch.Value);
}
}
}
Regex类:C#提供了一个名为System.Text.RegularExpressions的命名空间,其中包含了处理正则表达式的核心类,最常用的类是Regex类且提供了多种方法来执行正则表达式相关的操作。
Regex类用于创建和处理正则表达式,它主要有以下常用方法:
1)Match():尝试在输入字符串中找到第一个匹配的部分。
2)Matches():查找所有匹配项,并返回一个集合。
3)IsMatch():检查输入字符串是否符合正则表达式。
4)Replace():替换字符串中匹配的部分。
5)Split():根据正则表达式分割字符串。
cs
using System;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string input = "The price of the book is 30 dollars and the pen costs 5 dollars.";
// 正则表达式匹配所有数字
string pattern = @"\d+"; // \d+ 匹配一个或多个数字
// 使用 Regex.Match() 方法查找第一个匹配
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine("Found a match: " + match.Value);
}
// 使用 Regex.Matches() 方法查找所有匹配
MatchCollection matches = Regex.Matches(input, pattern);
foreach (Match m in matches)
{
Console.WriteLine("Found a match: " + m.Value);
}
}
}
C#异常处理
C#中的异常处理通过try、catch、finally块来管理运行时错误,异常是程序运行时出现的错误通常会导致程序崩溃,使用异常处理可以捕获错误并进行处理能确保程序的稳定性:
try:用来包裹可能发生异常的代码。
catch:用来捕获并处理异常,通常可以根据异常类型进行特定的处理。
finally:无论是否发生异常,都会执行的代码块,通常用于资源清理。
throw:当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。
cs
try
{
int result = 10 / 0; // 这将引发异常
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Cannot divide by zero: " + ex.Message);
}
finally
{
Console.WriteLine("This will always run.");
}
如果异常是直接或间接派生自System.Exception类可以抛出一个对象,在catch块中使用throw语句来抛出当前的对象,如下所示:
cs
Catch(Exception e)
{
...
Throw e
}
C#文件输入输出
在C#中文件输入输出用于处理文件的读取和写入操作,通过.NET提供的类库可以方便地进行文件操作,如读取文件内容、写入数据到文件、文件复制、删除等,C#中的文件I/O操作主要通过System.IO命名空间下的各种类来实现。
下表列出了一些 System.IO 命名空间中常用的非抽象类:
I/O 类 | 描述 |
---|---|
BinaryReader | 从二进制流读取原始数据。 |
BinaryWriter | 以二进制格式写入原始数据。 |
BufferedStream | 字节流的临时存储。 |
Directory | 有助于操作目录结构。 |
DirectoryInfo | 用于对目录执行操作。 |
DriveInfo | 提供驱动器的信息。 |
File | 有助于处理文件。 |
FileInfo | 用于对文件执行操作。 |
FileStream | 用于文件中任何位置的读写。 |
MemoryStream | 用于随机访问存储在内存中的数据流。 |
Path | 对路径信息执行操作。 |
StreamReader | 用于从字节流中读取字符。 |
StreamWriter | 用于向一个流中写入字符。 |
StringReader | 用于读取字符串缓冲区。 |
StringWriter | 用于写入字符串缓冲区。 |
File类提供了静态方法来进行文件的创建、删除、复制、移动、读取、写入等简化了常见的文件操作。
创建文件:
cs
using System.IO;
string path = "example.txt";
if (!File.Exists(path))
{
File.Create(path); // 创建文件
}
写入文件:File.WriteAllText和File.AppendAllText可用来写入文本到文件中
cs
string path = "example.txt";
string text = "Hello, world!";
File.WriteAllText(path, text); // 将文本写入文件
读取文件:File.ReadAllText用于读取整个文件的文本内容
cs
string path = "example.txt";
string content = File.ReadAllText(path); // 读取文件内容
Console.WriteLine(content);
复制文件:
cs
string sourcePath = "example.txt";
string destPath = "example_copy.txt";
File.Copy(sourcePath, destPath, true); // true 表示允许覆盖目标文件
删除文件:
cs
string path = "example.txt";
File.Delete(path); // 删除文件
StreamReader和StreamWriter类提供了更加灵活的文件读取和写入方式,可以逐行读取或写入文本
cs
// 读取文件内容:
using System.IO;
string path = "example.txt";
using (StreamReader reader = new StreamReader(path))
{
string content = reader.ReadToEnd(); // 读取整个文件内容
Console.WriteLine(content);
}
// 逐行读取文件:
using System.IO;
string path = "example.txt";
using (StreamReader reader = new StreamReader(path))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line); // 逐行读取并输出
}
}
// 写入文件内容:
using System.IO;
string path = "example.txt";
using (StreamWriter writer = new StreamWriter(path))
{
writer.WriteLine("Hello, world!"); // 写入一行文本
}
FileStream类允许我们以字节流的方式读取和写入文件,对于需要处理大文件或二进制数据时非常有用。
cs
// 读取二进制数据:
using System.IO;
string path = "example.bin";
using (FileStream fs = new FileStream(path, FileMode.Open))
{
byte[] data = new byte[fs.Length];
fs.Read(data, 0, data.Length); // 读取二进制数据
}
// 写入二进制数据:
using System.IO;
string path = "example.bin";
byte[] data = new byte[] { 1, 2, 3, 4, 5 };
using (FileStream fs = new FileStream(path, FileMode.Create))
{
fs.Write(data, 0, data.Length); // 写入二进制数据
}
Directory类提供了静态方法来创建、删除、移动和枚举目录(文件夹)等操作。
cs
// 创建目录:
string directoryPath = "myDirectory";
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath); // 创建目录
}
// 获取目录下的文件列表:
string directoryPath = "myDirectory";
string[] files = Directory.GetFiles(directoryPath); // 获取目录中的所有文件
foreach (var file in files)
{
Console.WriteLine(file);
}
// 删除目录:
string directoryPath = "myDirectory";
if (Directory.Exists(directoryPath))
{
Directory.Delete(directoryPath, true); // 删除目录及其所有内容
}
Path类提供了常见的路径操作方法,比如获取文件扩展名、文件名、合并路径等。
cs
// 合并路径:
string directoryPath = @"C:\Users";
string fileName = "example.txt";
string fullPath = Path.Combine(directoryPath, fileName); // 合并路径
Console.WriteLine(fullPath);
// 获取文件扩展名:
string filePath = "example.txt";
string extension = Path.GetExtension(filePath); // 获取文件扩展名
Console.WriteLine(extension);
FileInfo和DirectoryInfo类提供了文件和目录的详细信息,可以通过它们获取文件的大小、创建时间、修改时间等。
cs
// 获取文件信息:
FileInfo fileInfo = new FileInfo("example.txt");
Console.WriteLine("File size: " + fileInfo.Length);
Console.WriteLine("Last modified: " + fileInfo.LastWriteTime);
// 获取目录信息:
DirectoryInfo directoryInfo = new DirectoryInfo("myDirectory");
Console.WriteLine("Directory created: " + directoryInfo.CreationTime);
总结:C#提供了强大的文件输入输出功能可以方便地进行文件和目录的操作,通过System.IO命名空间中的类(如 File、StreamReader、StreamWriter、FileStream、Directory 等),可以进行各种文件和目录的创建、读取、写入、删除、复制等操作。在进行文件操作时确保正确处理异常并使用finally块来进行资源清理(如关闭文件流等),以提高程序的鲁棒性。