博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# 在自定义的控制台输出重定向类中整合调用方信息
阅读量:4034 次
发布时间:2019-05-24

本文共 9246 字,大约阅读时间需要 30 分钟。

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{    ///     /// [dlgcy] Console 输出重定向    /// 其他版本:DotNet.Utilities.WinformHelper.TextBoxWriter    /// 用法示例:    /// 在构造器里加上:Console.SetOut (new ConsoleWriter (s => { LogHelper.Write (s); }));    ///     /// 
///
/// public class Example /// { /// public Example() /// { /// Console.SetOut(new ConsoleWriter(s => { LogHelper.Write(s); })); /// } /// } /// ///
public class ConsoleWriter : TextWriter { private readonly Action
_Write; private readonly Action
_WriteLine; ///
/// Console 输出重定向 /// ///
日志方法委托(针对于 Write) ///
日志方法委托(针对于 WriteLine) public ConsoleWriter(Action
write, Action
writeLine) { _Write = write; _WriteLine = writeLine; } ///
/// Console 输出重定向 /// ///
日志方法委托 public ConsoleWriter(Action
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 方法如下即可:

/// /// 显示消息/// 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{    ///     /// [dlgcy] Console 输出重定向    /// 其他版本:DotNet.Utilities.WinformHelper.TextBoxWriter    /// 用法示例:    /// 在构造器里加上:Console.SetOut (new ConsoleWriter (s => { LogHelper.Write (s); }));    ///     /// 
///
/// public class Example /// { /// public Example() /// { /// Console.SetOut(new ConsoleWriter(s => { LogHelper.Write(s); })); /// } /// } /// ///
public class ConsoleWriter : TextWriter { private readonly Action
_Write; private readonly Action
_WriteLine; private readonly Action
_WriteCallerInfo; ///
/// Console 输出重定向 /// ///
日志方法委托(针对于 Write) ///
日志方法委托(针对于 WriteLine) public ConsoleWriter(Action
write, Action
writeLine) { _Write = write; _WriteLine = writeLine; } ///
/// Console 输出重定向 /// ///
日志方法委托 public ConsoleWriter(Action
write) { _Write = write; _WriteLine = write; } ///
/// Console 输出重定向(带调用方信息) /// ///
日志方法委托(后三个参数为 CallerFilePath、CallerMemberName、CallerLineNumber) public ConsoleWriter(Action
write) { _WriteCallerInfo = write; } ///
/// 使用 UTF-16 避免不必要的编码转换 /// public override Encoding Encoding => Encoding.Unicode; ///
/// 最低限度需要重写的方法 /// ///
消息 public override void Write(string value) { if (_WriteCallerInfo != null) { WriteWithCallerInfo(value); return; } _Write(value); } ///
/// 为提高效率直接处理一行的输出 /// ///
消息 public override void WriteLine(string value) { if (_WriteCallerInfo != null) { WriteWithCallerInfo(value); return; } _WriteLine(value); } ///
/// 带调用方信息进行写消息 /// ///
消息 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.WriteWithCallerInfo var 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 */         ///         /// 获取方法调用信息;        ///         /// 0 是本身,1 是调用方,2 是调用方的调用方... 以此类推         /// 
MethodInfo 对象
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; } } /// /// 方法调用信息 /// public class MethodInfo { /// /// 方法完整信息; /// public MethodBase Method { get; set; } /// /// 模块名 /// public string ModuleName { get; set; } /// /// 命名空间 /// public string Namespace { get; set; } /// /// 类名 /// public string ClassName { get; set; } /// /// 完整类名 /// public string FullClassName { get; set; } /// /// 方法名 /// public string MethodName { get; set; } /// /// 调用链 /// public string CallChain { get; set; } /// /// 行号 /// public int LineNumber { get; set; } /// /// 文件名 /// public string FileName { get; set; } } #endregion }}

 

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

 

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

 

四、后记及资源

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

 

上图所示的日志方法参见:《》

 

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

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

 

你可能感兴趣的文章
linux sfdisk partition
查看>>
ipconfig,ifconfig,iwconfig
查看>>
opensuse12.2 PL2303 minicom
查看>>
电平触发方式和边沿触发的区别
查看>>
网络视频服务器移植
查看>>
Encoding Schemes
查看>>
移植QT
查看>>
如此调用
查看>>
计算机的发展史
查看>>
带WiringPi库的交叉编译如何处理一
查看>>
带WiringPi库的交叉笔译如何处理二之软链接概念
查看>>
Spring事务的七种传播行为
查看>>
ES写入找不到主节点问题排查
查看>>
Java8 HashMap集合解析
查看>>
ArrayList集合解析
查看>>
欢迎使用CSDN-markdown编辑器
查看>>
Android计算器实现源码分析
查看>>
Android系统构架
查看>>
Android 跨应用程序访问窗口知识点总结
查看>>
各种排序算法的分析及java实现
查看>>