C# 在自定义的控制台输出重定向类中整合调用方信息

目录

C# 在自定义的控制台输出重定向类中整合调用方信息

一、前言

二、输出重定向基础版

三、输出重定向进阶版(传递调用方信息)

四、后记及资源

独立观察员 2021 年 1 月 6 日

一、前言

众所周知,在 .NET 的控制台应用程序(就是那种小黑框程序)中输出信息,使用的是控制台输出方法 Console.Write ("消息") 或 Console.WriteLine ("消息"),这两个方法称为标准输出。而在 Winform、WPF、网页程序中,使用这种方法输出的信息是没有地方显示的,在这些程序中,我们一般把信息输出到相应的显示控件中,或者写入日志中。

比如我这有个 Winform 测试程序,相关按钮的后台逻辑就是向控制台输出 “哈哈哈”,一般情况下,点击这个按钮,左边的消息框将不会有任何消息输出:

二、输出重定向基础版

但是这里却能显示出相关消息,是怎么回事呢?原来我在构造函数中添加了这么一句 —— Console.SetOut (new ConsoleWriter (ShowInfo));  —— 这就把原本输出到控制台的消息,重定向给了方法 ShowInfo 来进行输出,而 ShowInfo 方法内通过设置文本框的文本内容来达到了显示消息的效果:

其中的关键就是自定义类 ConsoleWriter(后面有新版):

using System;
using System.IO;
using System.Text;
/** 代码已托管 https://gitee.com/dlgcy/dotnetcodes/tree/dlgcy/DotNet.Utilities/ConsoleHelper*/
namespace DotNet.Utilities.ConsoleHelper
{/// <summary>/// [dlgcy] Console 输出重定向/// 其他版本:DotNet.Utilities.WinformHelper.TextBoxWriter/// 用法示例:/// 在构造器里加上:Console.SetOut (new ConsoleWriter (s => { LogHelper.Write (s); }));/// </summary>/// <example>/// <code>/// public class Example/// {///     public Example()///     {///         Console.SetOut(new ConsoleWriter(s => { LogHelper.Write(s); }));///     }/// }/// </code>/// </example>public class ConsoleWriter : TextWriter{private readonly Action<string> _Write;private readonly Action<string> _WriteLine;/// <summary>/// Console 输出重定向/// </summary>/// <param name="write"> 日志方法委托(针对于 Write)</param>/// <param name="writeLine"> 日志方法委托(针对于 WriteLine)</param>public ConsoleWriter(Action<string> write, Action<string> writeLine){_Write = write;_WriteLine = writeLine;}/// <summary>/// Console 输出重定向/// </summary>/// <param name="write"> 日志方法委托 </param>public ConsoleWriter(Action<string> write){_Write = write;_WriteLine = write;}// 使用 UTF-16 避免不必要的编码转换public override Encoding Encoding => Encoding.Unicode;// 最低限度需要重写的方法public override void Write(string value){_Write(value);}// 为提高效率直接处理一行的输出public override void WriteLine(string value){_WriteLine(value);}}
}

主要就是重写了 TextWriter 类的 Write 方法,然后在重写的 Write 方法中调用外部设置好的(通过构造函数)相关委托方法进行实际的信息输出。

以上就是之前的版本,工作地还不错。不过,当我们想在记录信息时同时记录调用方的信息时,问题就来了。

三、输出重定向进阶版(传递调用方信息)

要记录方法的调用方信息,我们很容易想到可以使用 C#5.0 中新增的获取调用方信息的方式,话不多说,改造 ShowInfo 方法如下即可:

/// <summary>
/// 显示消息
/// </summary>
private void ShowInfo(string info, [CallerFilePath] string filePath = "", [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0)
{TBInfo.Text += $"[{DateTime.Now:HH:mm:ss.ffff}][{filePath}][{memberName}][{lineNumber}] {info}\r\n\r\n";
}//private void ShowInfo(string info)
//{
//    TBInfo.Text += $"[{DateTime.Now:HH:mm:ss.ffff}] {info}\r\n\r\n";
//}

可以看到方法新增了以 CallerFilePath、CallerMemberName、CallerLineNumber 三个特性标注的三个可选参数,这样就能自动获得调用方法者的 文件名、成员名、行号了。

自然,构造函数中的重定向方法也需要更改:

public FormTest()
{InitializeComponent();//Console.SetOut(new ConsoleWriter(ShowInfo));Console.SetOut(new ConsoleWriter(msg => { ShowInfo(msg); }));
}

运行结果如下:

表面上看好像信息都有了,但是定睛一看,怎么调用成员显示的是 .ctor 而不是 BtnConsoleRedirect_Click ?行号显示的是 18 而不是 69?其实这里显示的信息是构造函数的(因为重定向语句在那里)。那么有没有办法显示实际的调用位置呢?我们继续改造。

这次改造的是重定向类 ConsoleWriter:

using System;
using System.IO;
using System.Text;
/** 代码已托管 https://gitee.com/dlgcy/dotnetcodes/tree/dlgcy/DotNet.Utilities/ConsoleHelper* 依赖:ClassHelper 类中获取调用信息的方法。*/
namespace DotNet.Utilities.ConsoleHelper
{/// <summary>/// [dlgcy] Console 输出重定向/// 其他版本:DotNet.Utilities.WinformHelper.TextBoxWriter/// 用法示例:/// 在构造器里加上:Console.SetOut (new ConsoleWriter (s => { LogHelper.Write (s); }));/// </summary>/// <example>/// <code>/// public class Example/// {///     public Example()///     {///         Console.SetOut(new ConsoleWriter(s => { LogHelper.Write(s); }));///     }/// }/// </code>/// </example>public class ConsoleWriter : TextWriter{private readonly Action<string> _Write;private readonly Action<string> _WriteLine;private readonly Action<string, string, string, int> _WriteCallerInfo;/// <summary>/// Console 输出重定向/// </summary>/// <param name="write"> 日志方法委托(针对于 Write)</param>/// <param name="writeLine"> 日志方法委托(针对于 WriteLine)</param>public ConsoleWriter(Action<string> write, Action<string> writeLine){_Write = write;_WriteLine = writeLine;}/// <summary>/// Console 输出重定向/// </summary>/// <param name="write"> 日志方法委托 </param>public ConsoleWriter(Action<string> write){_Write = write;_WriteLine = write;}/// <summary>/// Console 输出重定向(带调用方信息)/// </summary>/// <param name="write"> 日志方法委托(后三个参数为 CallerFilePath、CallerMemberName、CallerLineNumber)</param>public ConsoleWriter(Action<string, string, string, int> write){_WriteCallerInfo = write;}/// <summary>/// 使用 UTF-16 避免不必要的编码转换/// </summary>public override Encoding Encoding => Encoding.Unicode;/// <summary>/// 最低限度需要重写的方法/// </summary>/// <param name="value"> 消息 </param>public override void Write(string value){if (_WriteCallerInfo != null){WriteWithCallerInfo(value);return;}_Write(value);}/// <summary>/// 为提高效率直接处理一行的输出/// </summary>/// <param name="value"> 消息 </param>public override void WriteLine(string value){if (_WriteCallerInfo != null){WriteWithCallerInfo(value);return;}_WriteLine(value);}/// <summary>/// 带调用方信息进行写消息/// </summary>/// <param name="value"> 消息 </param>private void WriteWithCallerInfo(string value){//3、System.Console.WriteLine -> 2、System.IO.TextWriter + SyncTextWriter.WriteLine -> 1、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteLine -> 0、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteWithCallerInfovar callInfo = ClassHelper.GetMethodInfo(4);_WriteCallerInfo(value, callInfo?.FileName, callInfo?.MethodName, callInfo?.LineNumber ?? 0);}}
}

即新增一个包含了调用方信息三个参数的委托 _WriteCallerInfo,以及配套的构造方法,然后在 Write 方法中优先使用 _WriteCallerInfo 委托方法。另外,引入了一个获取调用方信息的方法(改造自《C# 获取当前方法信息,上端调用方方法信息以及方法调用链》):

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
/** 代码已托管 https://gitee.com/dlgcy/dotnetcodes/tree/dlgcy/DotNet.Utilities/Object*/
namespace DotNet.Utilities
{public class ClassHelper{#region 调用信息/* 参考:https://blog.csdn.net/m0_37886901/article/details/105266848 *//// <summary>/// 获取方法调用信息;/// </summary>/// <param name="index">0 是本身,1 是调用方,2 是调用方的调用方... 以此类推 </param>/// <returns>MethodInfo 对象 </returns>public static MethodInfo GetMethodInfo(int index){try{index++; // 由于这里是封装了方法,相当于上端想要获取本身,其实对于这里而言,上端的本身就是这里的上端,所以需要 + 1,以此类推var stack = new StackTrace(true);//0 是本身,1 是调用方,2 是调用方的调用方... 以此类推var currentFrame = stack.GetFrame(index);var method = currentFrame.GetMethod();var module = method.Module;var declaringType = method.DeclaringType;var stackFrames = stack.GetFrames();string callChain = string.Join(" -> ", stackFrames.Select((r, i) =>{if (i == 0) return null;var m = r.GetMethod();return $"{m.DeclaringType.FullName}.{m.Name}";}).Where(r => !string.IsNullOrWhiteSpace(r)).Reverse());return new MethodInfo(){Method = method,ModuleName = module.Name,Namespace = declaringType.Namespace,ClassName = declaringType.Name,FullClassName = declaringType.FullName,MethodName = method.Name,CallChain = callChain,LineNumber = currentFrame.GetFileLineNumber(),FileName = currentFrame.GetFileName(),};}catch (Exception ex){Console.WriteLine(ex);return null;}}/// <summary>/// 方法调用信息/// </summary>public class MethodInfo{/// <summary>/// 方法完整信息;/// </summary>public MethodBase Method { get; set; }/// <summary>/// 模块名/// </summary>public string ModuleName { get; set; }/// <summary>/// 命名空间/// </summary>public string Namespace { get; set; }/// <summary>/// 类名/// </summary>public string ClassName { get; set; }/// <summary>/// 完整类名/// </summary>public string FullClassName { get; set; }/// <summary>/// 方法名/// </summary>public string MethodName { get; set; }/// <summary>/// 调用链/// </summary>public string CallChain { get; set; }/// <summary>/// 行号/// </summary>public int LineNumber { get; set; }/// <summary>/// 文件名/// </summary>public string FileName { get; set; }}#endregion}
}

最后,恢复测试程序构造函数处的重定向语句为之前的写法,自动识别为调用 ConsoleWriter 中我们新增的那个构造函数:

运行,测试,可以看到方法名和行号都对了:

四、后记及资源

这种重定向的方式个人觉得挺方便的,比如在动态库中全都写成输出控制台的方式,然后在主程序构造函数中指定重定向;另外,还可用于转录到日志:

上图所示的日志方法参见:《『简易日志』NuGet 日志包 SimpleLogger》

本文测试程序相关代码:https://gitee.com/dlgcy/dotnetcodes/tree/dlgcy/DotNet.Utilities.Test

转录到日志的参考项目:https://gitee.com/dlgcy/WPFTemplate

C# 在自定义的控制台输出重定向类中整合调用方信息相关推荐

  1. php打印函数console,PHP内置Web Server探究(2)自定义PHP控制台输出console函数

    PHP内置Web Server探究(二)自定义PHP控制台输出console函数 我们在开发APP的服务器端,当和APP进行联调时通常需要实时跟踪URL请求和参数的接收情况. 但PHP并没有像Pyth ...

  2. 键盘录入多个数据,以0结束,要求在控制台输出所有数据中的最大值(ArrayList集合)

    思路:将录入的多个数据存储到ArrayList集合中,创建长度为集合大小的数组,将集合转为数组,然后对数组排序,这样数组中最后一位就是所有数据中的最大值. 完整代码如下: import java.ut ...

  3. mql5的include库文件中自定义enum类型在指标文件中的调用方式

    在mql5中编写指标,调用的include文件中某个类中使用了自定义ENUM枚举类型,如图所示: enum Smooth_Method { MODE_SMA_, //SMA MODE_EMA_, // ...

  4. python将控制台输出保存至文件

    很多时候在Linux系统下运行python程序时,控制台会输出一些有用的信息.为了方便保存这些信息,有时需要对这些信息进行保存.这里介绍几种将控制台输出保存到文件中的方式: 1 重定向标准输出流 重定 ...

  5. python文件输出-python将控制台输出保存至文件的方法

    很多时候在Linux系统下运行python程序时,控制台会输出一些有用的信息.为了方便保存这些信息,有时需要对这些信息进行保存.这里介绍几种将控制台输出保存到文件中的方式: 1 重定向标准输出流 重定 ...

  6. IntelliJ IDEA 自定义控制台输出多颜色格式功能 --- 安装Grep Console插件

    IntelliJ IDEA 自定义控制台输出多颜色格式功能 1. 打开IDEA设置面板 2. 点击插件(Plugins)安装Grep Console 3. 重启IDEA后设置颜色 4. 效果图 5. ...

  7. springboot项目控制台输出自定义图案

    如何在springboot项目控制台输出自定义图案呢? 本人的项目输出的是我名字的全拼,所以这里只介绍如何在控制台输出全拼名字. 1.http://patorjk.com/software/taag/ ...

  8. 将控制台输出重定向到textbox的dotnet类

    //实现思想是使用windows api CreatePipe 创建一个匿名管道 //接收控制台命令的输出,并产生委托事件. //具体实现见以下代码: using System; using Syst ...

  9. springboot 控制台输出错误信息_springboot(6)——整合日志

    概述 我们在平时开发项目的时候想知道程序运行情况一般可以使用sysout.print();打印一些关键的代码或者通过debug查看运行状态,但是对于这种sysout.print();很现任出现代码多余 ...

最新文章

  1. Hadoop入门进阶步步高(一)-环境准备
  2. 8.11zju集训日记
  3. matlab的灰色关联,灰色关联度Matlab代码
  4. c#扫描图片去黑边(扫描仪去黑边)
  5. CListControl的OnMouseMove和OnNcHitTest
  6. ExtJs 备忘录(1)—— Form表单(一) [ 控件使用 ]
  7. 小米11和vivox60买哪个?
  8. mut a:T 和a:mut T的区别
  9. 自然语言处理入门——RNN架构解析
  10. 电子工程师私藏的一个网站
  11. 新益华基层医疗系统使用方法_「热缩带」管道防腐新方向,聚乙烯热缩带安装使用方法...
  12. iOS 多媒体(一)音频播放
  13. 网络协议 终章 - GTP 协议:复杂的移动网络
  14. 设置无线网卡为AP工作模式(pi2和pi3)
  15. Java保留2位小数 JS保留2位小数 Java截取2位小数 Math.round((1.0/3)*100)/100.0
  16. 九宫格摆法_九宫格婚纱照摆法图片与技巧
  17. linux如何做动态壁纸实验报告,Ubuntu制作动态壁纸
  18. Git内部原理之深入解析环境变量
  19. Redis-学习笔记整理+汇总
  20. 反向代理方式实现IIS与Tomcat整合

热门文章

  1. 【NOIP必备攻略】 基本noilinux使用方法
  2. index.html 的默认301或者302跳转
  3. Tomcat 发布项目 conf/Catalina/localhost 配置 及数据源配置
  4. 智能路由器-OpenWRT 系列四 (挂载移动设备)
  5. Windows下Visual studio 2013 编译 Audacity
  6. js如何查看元素类型
  7. allegro下快捷键设置[转贴]
  8. 各个版本spring的jar包以及源码下载地址
  9. 如何判断一个字符串是不是纯数字
  10. JavaScript校验网址