|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在C#编程中,数据输出是一项基础且至关重要的技能。无论是简单的控制台应用程序,还是复杂的企业级系统,都需要通过适当的方式展示数据。本文将全面解析C#中的各种输出技巧,从最基础的控制台输出到文件输出,再到高级的格式化和重定向技术,帮助开发者掌握数据展示的全方位技能。
一、控制台输出基础
控制台输出是C#中最基本、最直接的输出方式,对于初学者和调试过程尤为重要。
1.1 Console.WriteLine() 与 Console.Write()
Console.WriteLine()和Console.Write()是C#中最常用的控制台输出方法。两者的区别在于前者输出后自动换行,后者不换行。
- using System;
- class Program
- {
- static void Main()
- {
- // 使用Console.WriteLine()输出并换行
- Console.WriteLine("这是第一行输出");
-
- // 使用Console.Write()输出不换行
- Console.Write("这是第二行输出的前半部分,");
- Console.WriteLine("这是后半部分");
-
- // 输出变量
- int age = 25;
- Console.WriteLine("我的年龄是:" + age);
- }
- }
复制代码
1.2 格式化控制台输出
C#提供了多种格式化输出的方法,使数据展示更加灵活和美观。
最简单的格式化方式是使用+运算符连接字符串和变量:
- string name = "张三";
- int score = 95;
- Console.WriteLine("姓名:" + name + ",分数:" + score);
复制代码
使用{}占位符和参数列表进行格式化:
- string name = "李四";
- double price = 12.99;
- Console.WriteLine("商品:{0},价格:{1:C}", name, price);
- // 输出:商品:李四,价格:¥12.99
复制代码
C# 6.0及以上版本支持字符串插值,使用$前缀:
- string name = "王五";
- DateTime now = DateTime.Now;
- Console.WriteLine($"用户{name}于{now:yyyy-MM-dd HH:mm:ss}登录系统");
复制代码
使用格式说明符控制数值、日期等的显示格式:
- double number = 1234.5678;
- Console.WriteLine($"固定小数点:{number:F2}"); // 输出:固定小数点:1234.57
- Console.WriteLine($"科学计数法:{number:E}"); // 输出:科学计数法:1.234568E+003
- Console.WriteLine($"百分比:{number:P}"); // 输出:百分比:123,456.78%
- DateTime date = DateTime.Now;
- Console.WriteLine($"短日期:{date:d}"); // 输出:短日期:2023/11/15
- Console.WriteLine($"长日期:{date:D}"); // 输出:长日期:2023年11月15日
- Console.WriteLine($"完整日期时间:{date:F}"); // 输出:完整日期时间:2023年11月15日 14:30:45
复制代码
1.3 控制台颜色和位置控制
通过控制台类可以设置输出文本的颜色和位置,增强用户体验。
- using System;
- class Program
- {
- static void Main()
- {
- // 设置文本颜色
- Console.ForegroundColor = ConsoleColor.Green;
- Console.WriteLine("这是绿色文本");
-
- // 设置背景颜色
- Console.BackgroundColor = ConsoleColor.Blue;
- Console.WriteLine("这是蓝色背景的文本");
-
- // 恢复默认颜色
- Console.ResetColor();
-
- // 设置光标位置
- Console.SetCursorPosition(10, 5);
- Console.WriteLine("位置在(10,5)的文本");
-
- // 获取窗口大小
- int width = Console.WindowWidth;
- int height = Console.WindowHeight;
- Console.WriteLine($"窗口宽度:{width},高度:{height}");
-
- // 清屏
- Console.Clear();
- Console.WriteLine("屏幕已清除");
- }
- }
复制代码
二、文件输出技术
除了控制台输出,文件输出是C#中另一种重要的数据展示方式,可以持久化保存数据。
2.1 使用File类进行基本文件操作
System.IO.File类提供了静态方法,简化了文件操作。
- using System;
- using System.IO;
- class Program
- {
- static void Main()
- {
- string filePath = "example.txt";
-
- // 写入文本文件(覆盖现有内容)
- File.WriteAllText(filePath, "这是第一行内容");
-
- // 追加文本到文件
- File.AppendAllText(filePath, "\n这是追加的内容");
-
- // 读取文件内容
- string content = File.ReadAllText(filePath);
- Console.WriteLine("文件内容:");
- Console.WriteLine(content);
-
- // 读取所有行
- string[] lines = File.ReadAllLines(filePath);
- Console.WriteLine("\n逐行显示:");
- foreach (var line in lines)
- {
- Console.WriteLine(line);
- }
- }
- }
复制代码
2.2 使用StreamWriter进行高级文件操作
StreamWriter类提供了更灵活的文件写入方式,支持自动刷新和编码设置。
- using System;
- using System.IO;
- using System.Text;
- class Program
- {
- static void Main()
- {
- string filePath = "advanced_example.txt";
-
- // 使用StreamWriter写入文件(覆盖模式)
- using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.UTF8))
- {
- writer.WriteLine("使用StreamWriter写入的第一行");
- writer.WriteLine("使用StreamWriter写入的第二行");
-
- // 格式化写入
- int id = 101;
- string name = "产品A";
- double price = 99.99;
- writer.WriteLine($"ID:{id}, 名称:{name}, 价格:{price:C}");
- }
-
- // 使用StreamWriter追加内容
- using (StreamWriter writer = new StreamWriter(filePath, true, Encoding.UTF8))
- {
- writer.WriteLine("这是追加的内容");
- }
-
- // 读取并显示文件内容
- Console.WriteLine("文件内容:");
- using (StreamReader reader = new StreamReader(filePath, Encoding.UTF8))
- {
- string line;
- while ((line = reader.ReadLine()) != null)
- {
- Console.WriteLine(line);
- }
- }
- }
- }
复制代码
2.3 二进制文件操作
对于非文本数据,可以使用BinaryWriter和BinaryReader进行二进制文件操作。
- using System;
- using System.IO;
- class Program
- {
- static void Main()
- {
- string filePath = "data.bin";
-
- // 写入二进制文件
- using (BinaryWriter writer = new BinaryWriter(File.Open(filePath, FileMode.Create)))
- {
- writer.Write(1.25); // 写入double
- writer.Write("hello"); // 写入字符串
- writer.Write(42); // 写入int
- writer.Write(true); // 写入bool
- }
-
- // 读取二进制文件
- using (BinaryReader reader = new BinaryReader(File.Open(filePath, FileMode.Open)))
- {
- double doubleValue = reader.ReadDouble();
- string stringValue = reader.ReadString();
- int intValue = reader.ReadInt32();
- bool boolValue = reader.ReadBoolean();
-
- Console.WriteLine($"Double: {doubleValue}");
- Console.WriteLine($"String: {stringValue}");
- Console.WriteLine($"Int: {intValue}");
- Console.WriteLine($"Bool: {boolValue}");
- }
- }
- }
复制代码
三、格式化输出进阶技巧
良好的格式化输出可以提高数据的可读性和专业性。
3.1 自定义格式化
通过实现IFormattable接口,可以为自定义类型定义格式化方式。
- using System;
- // 自定义类实现IFormattable接口
- public class Product : IFormattable
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public double Price { get; set; }
-
- public Product(int id, string name, double price)
- {
- Id = id;
- Name = name;
- Price = price;
- }
-
- // 实现ToString方法
- public override string ToString()
- {
- return ToString("G", null);
- }
-
- // 实现IFormattable接口的ToString方法
- public string ToString(string format, IFormatProvider formatProvider)
- {
- if (string.IsNullOrEmpty(format))
- format = "G";
-
- switch (format.ToUpper())
- {
- case "G":
- case "ID":
- return $"ID: {Id}";
- case "FULL":
- return $"ID: {Id}, Name: {Name}, Price: {Price:C}";
- case "NAME":
- return Name;
- case "PRICE":
- return Price.ToString("C", formatProvider);
- default:
- throw new FormatException($"格式'{format}'不被支持。");
- }
- }
- }
- class Program
- {
- static void Main()
- {
- Product product = new Product(1001, "笔记本电脑", 5999.99);
-
- Console.WriteLine(product.ToString("G")); // 输出:ID: 1001
- Console.WriteLine(product.ToString("FULL")); // 输出:ID: 1001, Name: 笔记本电脑, Price: ¥5,999.99
- Console.WriteLine(product.ToString("NAME")); // 输出:笔记本电脑
- Console.WriteLine(product.ToString("PRICE"));// 输出:¥5,999.99
-
- // 也可以直接使用格式字符串
- Console.WriteLine("{0:FULL}", product); // 输出:ID: 1001, Name: 笔记本电脑, Price: ¥5,999.99
- }
- }
复制代码
3.2 数字格式化
C#提供了丰富的数字格式化选项,可以满足各种显示需求。
- using System;
- using System.Globalization;
- class Program
- {
- static void Main()
- {
- double number = 1234567.8912;
-
- // 标准数字格式
- Console.WriteLine("货币格式 (C): " + number.ToString("C")); // 输出:¥1,234,567.89
- Console.WriteLine("十进制格式 (D): " + ((int)number).ToString("D")); // 输出:1234567
- Console.WriteLine("指数格式 (E): " + number.ToString("E")); // 输出:1.234568E+006
- Console.WriteLine("固定点格式 (F): " + number.ToString("F2")); // 输出:1234567.89
- Console.WriteLine("常规格式 (G): " + number.ToString("G")); // 输出:1234567.8912
- Console.WriteLine("数字格式 (N): " + number.ToString("N")); // 输出:1,234,567.89
- Console.WriteLine("百分比格式 (P): " + (number/10000).ToString("P")); // 输出:12,345.68%
-
- // 自定义数字格式
- Console.WriteLine("自定义格式1: " + number.ToString("###,###.##")); // 输出:1,234,567.89
- Console.WriteLine("自定义格式2: " + number.ToString("00000000.0000")); // 输出:01234567.8912
- Console.WriteLine("自定义格式3: " + number.ToString("#,##0.00;(#,##0.00);零")); // 输出:1,234,567.89
-
- // 使用不同区域设置
- CultureInfo usCulture = new CultureInfo("en-US");
- CultureInfo frCulture = new CultureInfo("fr-FR");
-
- Console.WriteLine("美国货币: " + number.ToString("C", usCulture)); // 输出:$1,234,567.89
- Console.WriteLine("法国货币: " + number.ToString("C", frCulture)); // 输出:1 234 567,89 €
- }
- }
复制代码
3.3 日期和时间格式化
日期和时间的格式化在日志记录、报表生成等场景中非常常见。
- using System;
- using System.Globalization;
- class Program
- {
- static void Main()
- {
- DateTime now = DateTime.Now;
-
- // 标准日期格式
- Console.WriteLine("短日期 (d): " + now.ToString("d")); // 输出:2023/11/15
- Console.WriteLine("长日期 (D): " + now.ToString("D")); // 输出:2023年11月15日
- Console.WriteLine("完整日期时间 (F): " + now.ToString("F")); // 输出:2023年11月15日 14:30:45
- Console.WriteLine("常规日期时间 (G): " + now.ToString("G")); // 输出:2023/11/15 14:30:45
- Console.WriteLine("月日 (M): " + now.ToString("M")); // 输出:11月15日
- Console.WriteLine("RFC1123 (R): " + now.ToString("R")); // 输出:Wed, 15 Nov 2023 14:30:45 GMT
- Console.WriteLine("可排序日期时间 (s): " + now.ToString("s")); // 输出:2023-11-15T14:30:45
- Console.WriteLine("短时间 (t): " + now.ToString("t")); // 输出:14:30
- Console.WriteLine("长时间 (T): " + now.ToString("T")); // 输出:14:30:45
- Console.WriteLine("通用可排序 (u): " + now.ToString("u")); // 输出:2023-11-15 14:30:45Z
- Console.WriteLine("通用完整 (U): " + now.ToString("U")); // 输出:2023年11月15日 6:30:45
-
- // 自定义日期格式
- Console.WriteLine("自定义格式1: " + now.ToString("yyyy-MM-dd")); // 输出:2023-11-15
- Console.WriteLine("自定义格式2: " + now.ToString("HH:mm:ss")); // 输出:14:30:45
- Console.WriteLine("自定义格式3: " + now.ToString("yyyy年MM月dd日 HH时mm分ss秒")); // 输出:2023年11月15日 14时30分45秒
-
- // 使用不同区域设置
- CultureInfo usCulture = new CultureInfo("en-US");
- CultureInfo frCulture = new CultureInfo("fr-FR");
-
- Console.WriteLine("美国日期: " + now.ToString("D", usCulture)); // 输出:Wednesday, November 15, 2023
- Console.WriteLine("法国日期: " + now.ToString("D", frCulture)); // 输出:mercredi 15 novembre 2023
- }
- }
复制代码
四、输出重定向技术
输出重定向技术允许将原本输出到一个地方的数据重定向到另一个地方,这在日志记录、测试等场景中非常有用。
4.1 控制台输出重定向
可以将控制台输出重定向到文件或其他目标。
- using System;
- using System.IO;
- class Program
- {
- static void Main()
- {
- // 保存原始的标准输出
- TextWriter originalOut = Console.Out;
-
- try
- {
- // 将控制台输出重定向到文件
- using (StreamWriter writer = new StreamWriter("console_output.txt"))
- {
- Console.SetOut(writer);
-
- // 这些输出将写入文件而不是控制台
- Console.WriteLine("这行内容将被写入文件");
- Console.WriteLine("当前时间: " + DateTime.Now);
-
- // 输出一些数据
- for (int i = 1; i <= 5; i++)
- {
- Console.WriteLine($"数字: {i}, 平方: {i * i}");
- }
- }
- }
- finally
- {
- // 恢复原始的标准输出
- Console.SetOut(originalOut);
- }
-
- // 现在输出将回到控制台
- Console.WriteLine("控制台输出已恢复");
- Console.WriteLine("文件内容:");
- Console.WriteLine(File.ReadAllText("console_output.txt"));
- }
- }
复制代码
4.2 使用Trace和Debug进行高级输出重定向
System.Diagnostics命名空间中的Trace和Debug类提供了更灵活的输出重定向机制。
- using System;
- using System.Diagnostics;
- class Program
- {
- static void Main()
- {
- // 创建文本监听器并添加到Trace.Listeners
- TextWriterTraceListener fileListener = new TextWriterTraceListener("trace_output.txt");
- Trace.Listeners.Add(fileListener);
-
- // 也可以添加控制台监听器
- Trace.Listeners.Add(new ConsoleTraceListener());
-
- // 启用自动刷新
- Trace.AutoFlush = true;
-
- // 使用Trace输出
- Trace.WriteLine("应用程序启动");
- Trace.WriteLine("当前时间: " + DateTime.Now);
-
- // 带类别的输出
- Trace.WriteLine("用户登录成功", "信息");
- Trace.WriteLine("数据库连接失败", "错误");
-
- // 条件输出
- bool debugMode = true;
- Trace.WriteLineIf(debugMode, "调试模式已启用", "调试");
-
- // 缩进输出
- Trace.Indent();
- Trace.WriteLine("这是缩进的输出");
- Trace.Unindent();
-
- // 关闭监听器
- Trace.Close();
-
- Console.WriteLine("Trace输出已完成,请查看trace_output.txt文件");
- }
- }
复制代码
4.3 使用StringWriter捕获输出
StringWriter允许将输出捕获到内存中的字符串,这对于测试和数据处理非常有用。
- using System;
- using System.IO;
- class Program
- {
- static void Main()
- {
- // 创建StringWriter
- StringWriter stringWriter = new StringWriter();
-
- // 保存原始控制台输出
- TextWriter originalOut = Console.Out;
-
- try
- {
- // 将控制台输出重定向到StringWriter
- Console.SetOut(stringWriter);
-
- // 输出一些内容
- Console.WriteLine("这行内容将被捕获到字符串");
- Console.WriteLine("当前时间: " + DateTime.Now);
-
- // 输出一个表格
- Console.WriteLine("ID\t名称\t价格");
- Console.WriteLine("1\t产品A\t¥99.99");
- Console.WriteLine("2\t产品B\t¥199.99");
- Console.WriteLine("3\t产品C\t¥299.99");
- }
- finally
- {
- // 恢复原始控制台输出
- Console.SetOut(originalOut);
- }
-
- // 获取捕获的输出
- string capturedOutput = stringWriter.ToString();
-
- // 现在可以处理捕获的输出
- Console.WriteLine("捕获的输出:");
- Console.WriteLine(capturedOutput);
-
- // 例如,可以将捕获的输出写入文件
- File.WriteAllText("captured_output.txt", capturedOutput);
- Console.WriteLine("捕获的输出已保存到文件");
- }
- }
复制代码
五、常见问题及解决方案
在实际开发中,我们经常会遇到各种输出相关的问题。本节将介绍一些常见问题及其解决方案。
5.1 中文乱码问题
在处理中文字符时,经常会遇到乱码问题,这通常是由于编码不一致导致的。
- using System;
- using System.IO;
- using System.Text;
- class Program
- {
- static void Main()
- {
- string filePath = "chinese_text.txt";
- string chineseText = "这是一段中文文本,包含各种汉字:你好,世界!";
-
- // 问题:使用默认编码写入可能导致乱码
- File.WriteAllText(filePath, chineseText);
- string content1 = File.ReadAllText(filePath);
- Console.WriteLine("默认编码读取: " + content1);
-
- // 解决方案:明确指定UTF-8编码
- File.WriteAllText(filePath, chineseText, Encoding.UTF8);
- string content2 = File.ReadAllText(filePath, Encoding.UTF8);
- Console.WriteLine("UTF-8编码读取: " + content2);
-
- // 使用StreamWriter和StreamReader指定编码
- using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.UTF8))
- {
- writer.WriteLine("使用StreamWriter写入的中文内容");
- }
-
- using (StreamReader reader = new StreamReader(filePath, Encoding.UTF8))
- {
- string line;
- while ((line = reader.ReadLine()) != null)
- {
- Console.WriteLine("StreamReader读取: " + line);
- }
- }
-
- // 处理不同编码的文件
- string gbkFilePath = "gbk_text.txt";
-
- // 获取GBK编码(需要先注册编码提供程序)
- Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- Encoding gbkEncoding = Encoding.GetEncoding("GBK");
-
- // 使用GBK编码写入
- File.WriteAllText(gbkFilePath, "这是GBK编码的中文文本", gbkEncoding);
-
- // 使用GBK编码读取
- string gbkContent = File.ReadAllText(gbkFilePath, gbkEncoding);
- Console.WriteLine("GBK编码读取: " + gbkContent);
- }
- }
复制代码
5.2 大文件处理问题
处理大文件时,直接读取整个文件到内存可能导致内存不足问题。
- using System;
- using System.IO;
- class Program
- {
- static void Main()
- {
- string largeFilePath = "large_file.txt";
-
- // 问题:一次性读取大文件可能导致内存不足
- // string allContent = File.ReadAllText(largeFilePath); // 不推荐用于大文件
-
- // 解决方案1:逐行读取
- Console.WriteLine("逐行读取大文件:");
- using (StreamReader reader = new StreamReader(largeFilePath))
- {
- string line;
- int lineCount = 0;
- while ((line = reader.ReadLine()) != null)
- {
- lineCount++;
- // 处理每一行
- if (lineCount <= 5) // 只显示前5行作为示例
- {
- Console.WriteLine($"行 {lineCount}: {line}");
- }
- }
- Console.WriteLine($"总行数: {lineCount}");
- }
-
- // 解决方案2:分块读取
- Console.WriteLine("\n分块读取大文件:");
- using (FileStream fs = new FileStream(largeFilePath, FileMode.Open, FileAccess.Read))
- {
- byte[] buffer = new byte[4096]; // 4KB的缓冲区
- int bytesRead;
- long totalBytesRead = 0;
-
- while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
- {
- totalBytesRead += bytesRead;
- // 处理读取的数据
- string chunk = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
- Console.WriteLine($"已读取 {totalBytesRead} 字节,当前块大小: {bytesRead}");
-
- // 只处理第一块作为示例
- break;
- }
- }
-
- // 解决方案3:使用内存映射文件处理超大文件
- Console.WriteLine("\n使用内存映射文件处理超大文件:");
- using (var mmf = System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile(largeFilePath))
- {
- using (var accessor = mmf.CreateViewAccessor())
- {
- long fileSize = new FileInfo(largeFilePath).Length;
- Console.WriteLine($"文件大小: {fileSize} 字节");
-
- // 读取文件开头和结尾的一些内容
- byte[] startBytes = new byte[100];
- byte[] endBytes = new byte[100];
-
- accessor.ReadArray(0, startBytes, 0, startBytes.Length);
- accessor.ReadArray(fileSize - endBytes.Length, endBytes, 0, endBytes.Length);
-
- string startText = System.Text.Encoding.UTF8.GetString(startBytes);
- string endText = System.Text.Encoding.UTF8.GetString(endBytes);
-
- Console.WriteLine($"文件开头: {startText}");
- Console.WriteLine($"文件结尾: {endText}");
- }
- }
- }
- }
复制代码
5.3 性能优化问题
频繁的I/O操作可能影响程序性能,特别是在需要大量输出时。
- using System;
- using System.Diagnostics;
- using System.IO;
- class Program
- {
- static void Main()
- {
- string filePath1 = "slow_output.txt";
- string filePath2 = "fast_output.txt";
- int lineCount = 10000;
-
- // 问题:频繁的文件打开/关闭操作导致性能低下
- Stopwatch sw = Stopwatch.StartNew();
- for (int i = 0; i < lineCount; i++)
- {
- File.AppendAllText(filePath1, $"行 {i}: 这是一些示例文本\n");
- }
- sw.Stop();
- Console.WriteLine($"慢速方法耗时: {sw.ElapsedMilliseconds} 毫秒");
-
- // 解决方案1:使用StreamWriter并保持文件打开
- sw.Restart();
- using (StreamWriter writer = new StreamWriter(filePath2))
- {
- for (int i = 0; i < lineCount; i++)
- {
- writer.WriteLine($"行 {i}: 这是一些示例文本");
- }
- }
- sw.Stop();
- Console.WriteLine($"快速方法1耗时: {sw.ElapsedMilliseconds} 毫秒");
-
- // 解决方案2:使用StringBuilder构建内容后一次性写入
- string filePath3 = "fast_output2.txt";
- sw.Restart();
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < lineCount; i++)
- {
- sb.AppendLine($"行 {i}: 这是一些示例文本");
- }
- File.WriteAllText(filePath3, sb.ToString());
- sw.Stop();
- Console.WriteLine($"快速方法2耗时: {sw.ElapsedMilliseconds} 毫秒");
-
- // 解决方案3:使用缓冲区
- string filePath4 = "fast_output3.txt";
- sw.Restart();
- using (StreamWriter writer = new StreamWriter(filePath4))
- {
- // 设置更大的缓冲区
- writer.AutoFlush = false; // 禁用自动刷新
-
- for (int i = 0; i < lineCount; i++)
- {
- writer.WriteLine($"行 {i}: 这是一些示例文本");
-
- // 每1000行刷新一次
- if (i % 1000 == 0)
- {
- writer.Flush();
- }
- }
-
- // 最后刷新
- writer.Flush();
- }
- sw.Stop();
- Console.WriteLine($"快速方法3耗时: {sw.ElapsedMilliseconds} 毫秒");
-
- // 解决方案4:并行处理(适用于大量独立写入操作)
- string filePath5 = "parallel_output.txt";
- sw.Restart();
-
- // 先创建文件
- File.WriteAllText(filePath5, "");
-
- // 使用并行处理
- object lockObject = new object();
- System.Threading.Tasks.Parallel.For(0, lineCount, i =>
- {
- string line = $"行 {i}: 这是一些示例文本\n";
-
- // 使用锁确保线程安全
- lock (lockObject)
- {
- File.AppendAllText(filePath5, line);
- }
- });
-
- sw.Stop();
- Console.WriteLine($"并行方法耗时: {sw.ElapsedMilliseconds} 毫秒");
- }
- }
复制代码
5.4 异常处理问题
文件操作可能会因各种原因失败,如文件不存在、权限不足等,适当的异常处理非常重要。
六、实际应用示例
6.1 日志记录系统
下面是一个简单的日志记录系统实现,展示了如何将输出同时定向到控制台和文件。
- using System;
- using System.IO;
- // 日志级别枚举
- public enum LogLevel
- {
- Debug,
- Info,
- Warning,
- Error,
- Fatal
- }
- // 简单的日志记录器
- public class Logger
- {
- private readonly string _logFilePath;
- private readonly object _lockObject = new object();
-
- public Logger(string logFilePath)
- {
- _logFilePath = logFilePath;
-
- // 确保日志目录存在
- string directory = Path.GetDirectoryName(logFilePath);
- if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
- {
- Directory.CreateDirectory(directory);
- }
- }
-
- public void Log(LogLevel level, string message)
- {
- string logEntry = FormatLogEntry(level, message);
-
- // 输出到控制台
- Console.WriteLine(logEntry);
-
- // 写入文件(使用锁确保线程安全)
- lock (_lockObject)
- {
- try
- {
- File.AppendAllText(_logFilePath, logEntry + Environment.NewLine);
- }
- catch (Exception ex)
- {
- Console.WriteLine($"写入日志文件失败: {ex.Message}");
- }
- }
- }
-
- public void Debug(string message) => Log(LogLevel.Debug, message);
- public void Info(string message) => Log(LogLevel.Info, message);
- public void Warning(string message) => Log(LogLevel.Warning, message);
- public void Error(string message) => Log(LogLevel.Error, message);
- public void Fatal(string message) => Log(LogLevel.Fatal, message);
-
- private string FormatLogEntry(LogLevel level, string message)
- {
- return $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{level}] {message}";
- }
- }
- class Program
- {
- static void Main()
- {
- // 创建日志记录器
- Logger logger = new Logger("logs/app_" + DateTime.Now.ToString("yyyyMMdd") + ".log");
-
- // 记录不同级别的日志
- logger.Debug("应用程序启动,调试模式已启用");
- logger.Info("用户登录成功,用户ID: 1001");
- logger.Warning("数据库连接缓慢,响应时间: 1500ms");
- logger.Error("无法处理用户请求,错误代码: 500");
- logger.Fatal("系统发生严重错误,即将关闭");
-
- // 模拟一个业务流程
- try
- {
- logger.Info("开始处理订单");
-
- // 模拟处理过程
- for (int i = 0; i < 5; i++)
- {
- logger.Debug($"处理订单项 {i + 1}/5");
- System.Threading.Thread.Sleep(100); // 模拟处理时间
- }
-
- logger.Info("订单处理完成");
- }
- catch (Exception ex)
- {
- logger.Error($"订单处理失败: {ex.Message}");
- }
-
- Console.WriteLine("日志记录完成,请查看日志文件");
- }
- }
复制代码
6.2 数据导出工具
下面是一个数据导出工具的示例,展示了如何将数据格式化输出到不同格式的文件。
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- // 产品类
- public class Product
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Category { get; set; }
- public double Price { get; set; }
- public int Stock { get; set; }
-
- public Product(int id, string name, string category, double price, int stock)
- {
- Id = id;
- Name = name;
- Category = category;
- Price = price;
- Stock = stock;
- }
- }
- // 数据导出器
- public class DataExporter
- {
- private readonly List<Product> _products;
-
- public DataExporter(List<Product> products)
- {
- _products = products;
- }
-
- // 导出为CSV格式
- public void ExportToCsv(string filePath)
- {
- StringBuilder sb = new StringBuilder();
-
- // 添加CSV头
- sb.AppendLine("ID,Name,Category,Price,Stock");
-
- // 添加数据行
- foreach (var product in _products)
- {
- // 处理可能包含逗号的字段(用引号包围)
- string name = product.Name.Contains(",") ? $""{product.Name}"" : product.Name;
- string category = product.Category.Contains(",") ? $""{product.Category}"" : product.Category;
-
- sb.AppendLine($"{product.Id},{name},{category},{product.Price},{product.Stock}");
- }
-
- File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
- }
-
- // 导出为HTML表格
- public void ExportToHtml(string filePath)
- {
- StringBuilder sb = new StringBuilder();
-
- // HTML头部
- sb.AppendLine("<!DOCTYPE html>");
- sb.AppendLine("<html>");
- sb.AppendLine("<head>");
- sb.AppendLine("<meta charset="UTF-8">");
- sb.AppendLine("<title>产品列表</title>");
- sb.AppendLine("<style>");
- sb.AppendLine("table { border-collapse: collapse; width: 100%; }");
- sb.AppendLine("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }");
- sb.AppendLine("th { background-color: #f2f2f2; }");
- sb.AppendLine("tr:nth-child(even) { background-color: #f9f9f9; }");
- sb.AppendLine("</style>");
- sb.AppendLine("</head>");
- sb.AppendLine("<body>");
- sb.AppendLine("<h1>产品列表</h1>");
- sb.AppendLine("<table>");
-
- // 表头
- sb.AppendLine("<tr>");
- sb.AppendLine("<th>ID</th>");
- sb.AppendLine("<th>名称</th>");
- sb.AppendLine("<th>类别</th>");
- sb.AppendLine("<th>价格</th>");
- sb.AppendLine("<th>库存</th>");
- sb.AppendLine("</tr>");
-
- // 数据行
- foreach (var product in _products)
- {
- sb.AppendLine("<tr>");
- sb.AppendLine($"<td>{product.Id}</td>");
- sb.AppendLine($"<td>{product.Name}</td>");
- sb.AppendLine($"<td>{product.Category}</td>");
- sb.AppendLine($"<td>{product.Price:C}</td>");
- sb.AppendLine($"<td>{product.Stock}</td>");
- sb.AppendLine("</tr>");
- }
-
- // HTML尾部
- sb.AppendLine("</table>");
- sb.AppendLine("</body>");
- sb.AppendLine("</html>");
-
- File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
- }
-
- // 导出为XML格式
- public void ExportToXml(string filePath)
- {
- StringBuilder sb = new StringBuilder();
-
- // XML声明和根元素
- sb.AppendLine("<?xml version="1.0" encoding="UTF-8"?>");
- sb.AppendLine("<Products>");
-
- // 产品元素
- foreach (var product in _products)
- {
- sb.AppendLine(" <Product>");
- sb.AppendLine($" <Id>{product.Id}</Id>");
- sb.AppendLine($" <Name>{EscapeXml(product.Name)}</Name>");
- sb.AppendLine($" <Category>{EscapeXml(product.Category)}</Category>");
- sb.AppendLine($" <Price>{product.Price}</Price>");
- sb.AppendLine($" <Stock>{product.Stock}</Stock>");
- sb.AppendLine(" </Product>");
- }
-
- // 根元素结束
- sb.AppendLine("</Products>");
-
- File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
- }
-
- // 导出为JSON格式
- public void ExportToJson(string filePath)
- {
- StringBuilder sb = new StringBuilder();
-
- sb.AppendLine("[");
-
- for (int i = 0; i < _products.Count; i++)
- {
- var product = _products[i];
-
- sb.AppendLine(" {");
- sb.AppendLine($" "Id": {product.Id},");
- sb.AppendLine($" "Name": "{EscapeJson(product.Name)}",");
- sb.AppendLine($" "Category": "{EscapeJson(product.Category)}",");
- sb.AppendLine($" "Price": {product.Price.ToString(System.Globalization.CultureInfo.InvariantCulture)},");
- sb.AppendLine($" "Stock": {product.Stock}");
- sb.Append(i < _products.Count - 1 ? " }," : " }");
-
- if (i < _products.Count - 1)
- {
- sb.AppendLine();
- }
- }
-
- sb.AppendLine();
- sb.AppendLine("]");
-
- File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
- }
-
- // 转义XML特殊字符
- private string EscapeXml(string text)
- {
- return text.Replace("&", "&")
- .Replace("<", "<")
- .Replace(">", ">")
- .Replace(""", """)
- .Replace("'", "'");
- }
-
- // 转义JSON特殊字符
- private string EscapeJson(string text)
- {
- return text.Replace("\", "\\\")
- .Replace(""", "\\"")
- .Replace("\b", "\\b")
- .Replace("\f", "\\f")
- .Replace("\n", "\\n")
- .Replace("\r", "\\r")
- .Replace("\t", "\\t");
- }
- }
- class Program
- {
- static void Main()
- {
- // 创建示例数据
- List<Product> products = new List<Product>
- {
- new Product(1, "笔记本电脑", "电子产品", 5999.99, 50),
- new Product(2, "智能手机", "电子产品", 3999.50, 100),
- new Product(3, "办公椅,人体工学", "家具", 899.00, 25),
- new Product(4, "《C#编程指南》", "图书", 89.90, 200),
- new Product(5, "无线鼠标", "电脑配件", 129.00, 150)
- };
-
- // 创建数据导出器
- DataExporter exporter = new DataExporter(products);
-
- // 导出为不同格式
- string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
-
- exporter.ExportToCsv($"products_{timestamp}.csv");
- Console.WriteLine("CSV导出完成");
-
- exporter.ExportToHtml($"products_{timestamp}.html");
- Console.WriteLine("HTML导出完成");
-
- exporter.ExportToXml($"products_{timestamp}.xml");
- Console.WriteLine("XML导出完成");
-
- exporter.ExportToJson($"products_{timestamp}.json");
- Console.WriteLine("JSON导出完成");
-
- Console.WriteLine("所有格式的数据导出已完成");
- }
- }
复制代码
6.3 报表生成器
下面是一个简单的报表生成器示例,展示了如何格式化输出复杂的报表数据。
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- // 销售数据类
- public class SalesData
- {
- public string Region { get; set; }
- public string Product { get; set; }
- public int Quantity { get; set; }
- public double UnitPrice { get; set; }
- public double Total => Quantity * UnitPrice;
-
- public SalesData(string region, string product, int quantity, double unitPrice)
- {
- Region = region;
- Product = product;
- Quantity = quantity;
- UnitPrice = unitPrice;
- }
- }
- // 报表生成器
- public class ReportGenerator
- {
- private readonly List<SalesData> _salesData;
-
- public ReportGenerator(List<SalesData> salesData)
- {
- _salesData = salesData;
- }
-
- // 生成文本报表
- public void GenerateTextReport(string filePath)
- {
- StringBuilder sb = new StringBuilder();
-
- // 报表标题
- sb.AppendLine("========================================");
- sb.AppendLine(" 销售数据报表");
- sb.AppendLine("========================================");
- sb.AppendLine();
-
- // 按地区分组
- var groupedByRegion = GroupDataByRegion();
-
- foreach (var regionGroup in groupedByRegion)
- {
- // 地区标题
- sb.AppendLine($"地区: {regionGroup.Key}");
- sb.AppendLine("----------------------------------------");
-
- // 表头
- sb.AppendLine($"{"产品",-20} {"数量",10} {"单价",12} {"总额",12}");
- sb.AppendLine("----------------------------------------");
-
- double regionTotal = 0;
-
- // 地区数据
- foreach (var data in regionGroup)
- {
- sb.AppendLine($"{data.Product,-20} {data.Quantity,10} {data.UnitPrice,12:C} {data.Total,12:C}");
- regionTotal += data.Total;
- }
-
- // 地区小计
- sb.AppendLine("----------------------------------------");
- sb.AppendLine($"{"地区小计:",-20} {"",10} {"",12} {regionTotal,12:C}");
- sb.AppendLine();
- }
-
- // 总计
- double grandTotal = CalculateGrandTotal();
- sb.AppendLine("========================================");
- sb.AppendLine($"{"总计:",-20} {"",10} {"",12} {grandTotal,12:C}");
- sb.AppendLine("========================================");
- sb.AppendLine();
-
- // 报表生成信息
- sb.AppendLine($"报表生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
- sb.AppendLine($"数据条目数: {_salesData.Count}");
-
- File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
- }
-
- // 生成HTML报表
- public void GenerateHtmlReport(string filePath)
- {
- StringBuilder sb = new StringBuilder();
-
- // HTML头部
- sb.AppendLine("<!DOCTYPE html>");
- sb.AppendLine("<html>");
- sb.AppendLine("<head>");
- sb.AppendLine("<meta charset="UTF-8">");
- sb.AppendLine("<title>销售数据报表</title>");
- sb.AppendLine("<style>");
- sb.AppendLine("body { font-family: Arial, sans-serif; margin: 20px; }");
- sb.AppendLine("h1 { color: #333; text-align: center; }");
- sb.AppendLine("h2 { color: #444; border-bottom: 1px solid #ddd; padding-bottom: 5px; }");
- sb.AppendLine("table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }");
- sb.AppendLine("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }");
- sb.AppendLine("th { background-color: #f2f2f2; }");
- sb.AppendLine("tr:nth-child(even) { background-color: #f9f9f9; }");
- sb.AppendLine(".total-row { font-weight: bold; background-color: #e9e9e9; }");
- sb.AppendLine(".grand-total { font-weight: bold; background-color: #d9d9d9; }");
- sb.AppendLine(".footer { margin-top: 30px; font-size: 0.9em; color: #666; text-align: right; }");
- sb.AppendLine("</style>");
- sb.AppendLine("</head>");
- sb.AppendLine("<body>");
-
- // 报表标题
- sb.AppendLine("<h1>销售数据报表</h1>");
-
- // 按地区分组
- var groupedByRegion = GroupDataByRegion();
-
- foreach (var regionGroup in groupedByRegion)
- {
- // 地区标题
- sb.AppendLine($"<h2>地区: {regionGroup.Key}</h2>");
-
- // 地区表格
- sb.AppendLine("<table>");
- sb.AppendLine("<tr>");
- sb.AppendLine("<th>产品</th>");
- sb.AppendLine("<th>数量</th>");
- sb.AppendLine("<th>单价</th>");
- sb.AppendLine("<th>总额</th>");
- sb.AppendLine("</tr>");
-
- double regionTotal = 0;
-
- // 地区数据
- foreach (var data in regionGroup)
- {
- sb.AppendLine("<tr>");
- sb.AppendLine($"<td>{data.Product}</td>");
- sb.AppendLine($"<td>{data.Quantity}</td>");
- sb.AppendLine($"<td>{data.UnitPrice:C}</td>");
- sb.AppendLine($"<td>{data.Total:C}</td>");
- sb.AppendLine("</tr>");
-
- regionTotal += data.Total;
- }
-
- // 地区小计
- sb.AppendLine("<tr class="total-row">");
- sb.AppendLine("<td>地区小计</td>");
- sb.AppendLine("<td></td>");
- sb.AppendLine("<td></td>");
- sb.AppendLine($"<td>{regionTotal:C}</td>");
- sb.AppendLine("</tr>");
-
- sb.AppendLine("</table>");
- }
-
- // 总计
- double grandTotal = CalculateGrandTotal();
- sb.AppendLine("<table>");
- sb.AppendLine("<tr class="grand-total">");
- sb.AppendLine("<td>总计</td>");
- sb.AppendLine("<td></td>");
- sb.AppendLine("<td></td>");
- sb.AppendLine($"<td>{grandTotal:C}</td>");
- sb.AppendLine("</tr>");
- sb.AppendLine("</table>");
-
- // 报表生成信息
- sb.AppendLine("<div class="footer">");
- sb.AppendLine($"<p>报表生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}</p>");
- sb.AppendLine($"<p>数据条目数: {_salesData.Count}</p>");
- sb.AppendLine("</div>");
-
- // HTML尾部
- sb.AppendLine("</body>");
- sb.AppendLine("</html>");
-
- File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
- }
-
- // 按地区分组数据
- private IGrouping<string, SalesData>[] GroupDataByRegion()
- {
- var grouped = _salesData
- .GroupBy(data => data.Region)
- .OrderBy(group => group.Key)
- .ToArray();
-
- return grouped;
- }
-
- // 计算总计
- private double CalculateGrandTotal()
- {
- return _salesData.Sum(data => data.Total);
- }
- }
- class Program
- {
- static void Main()
- {
- // 创建示例销售数据
- List<SalesData> salesData = new List<SalesData>
- {
- new SalesData("华东", "笔记本电脑", 50, 5999.99),
- new SalesData("华东", "智能手机", 120, 3999.50),
- new SalesData("华东", "平板电脑", 80, 2999.00),
- new SalesData("华南", "笔记本电脑", 30, 5999.99),
- new SalesData("华南", "智能手机", 150, 3999.50),
- new SalesData("华南", "智能手表", 200, 1299.00),
- new SalesData("华北", "笔记本电脑", 40, 5999.99),
- new SalesData("华北", "智能手机", 100, 3999.50),
- new SalesData("华北", "平板电脑", 60, 2999.00),
- new SalesData("华北", "智能手表", 180, 1299.00)
- };
-
- // 创建报表生成器
- ReportGenerator reportGenerator = new ReportGenerator(salesData);
-
- // 生成文本报表
- string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
- string textReportPath = $"sales_report_{timestamp}.txt";
- reportGenerator.GenerateTextReport(textReportPath);
- Console.WriteLine($"文本报表已生成: {textReportPath}");
-
- // 生成HTML报表
- string htmlReportPath = $"sales_report_{timestamp}.html";
- reportGenerator.GenerateHtmlReport(htmlReportPath);
- Console.WriteLine($"HTML报表已生成: {htmlReportPath}");
-
- Console.WriteLine("报表生成完成");
- }
- }
复制代码
结论
本文全面解析了C#中的各种输出技巧,从基础的控制台输出到文件输出,再到高级的格式化和重定向技术。通过详细的代码示例,我们展示了如何在实际开发中应用这些技巧来解决各种数据展示问题。
掌握这些输出技巧对于C#开发者来说至关重要,它们不仅能帮助我们更好地展示数据,还能提高程序的性能和可靠性。无论是简单的控制台应用程序,还是复杂的企业级系统,良好的输出技术都能使数据展示更加清晰、专业。
希望本文能帮助读者深入理解C#中的输出技术,并在实际开发中灵活应用这些知识,解决各种数据展示相关的挑战。 |
|