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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac 上mysql怎么卸载不了_mac的mysql怎么卸载?

Mac下卸载mysql的方法&#xff1a;1、关闭mysql查看mysql是否启动&#xff1a;ps -ef |grep mysql输入&#xff1a;kill -9 (./mysqld前面第二个数字&#xff0c;这里是627) 然后回车&#xff0c;关闭mysql。2、卸载&#xff1a;在Mac终端使用下面的命令删除所有mysql文件即可s…

C#实现网页加载后将页面截取成长图片

背景最近再做一个需求&#xff0c;需要对网页生成预览图&#xff0c;如下图但是网页千千万&#xff0c;总不能一个个打开&#xff0c;截图吧&#xff1b;于是想着能不能使用代码来实现网页的截图。其实要实现这个功能&#xff0c;无非就是要么实现一个仿真浏览器&#xff0c;要…

mysql 8 配置参数优化_mysql8 参考手册--配置非持久性优化器统计参数

本节介绍如何配置非持久性优化器统计信息。当innodb_stats_persistentOFF或使用创建或更改单个表时&#xff0c;Optimizer统计信息不会保留在磁盘 上 STATS_PERSISTENT0。相反&#xff0c;统计信息存储在内存中&#xff0c;并且在服务器关闭时会丢失。统计信息还可以通过某些操…

“既然计划没有变化快,那制订计划还有个卵用啊!”

这是头哥侃码的第229篇原创每年年初&#xff0c;我的朋友圈里都会炸出不少在打完鸡血之后&#xff0c;迫不及待向全世界宣告自己 “新年Flag” 的人。有的人&#xff0c;把健身、养生设为目标&#xff0c;什么不暴瘦20斤不换头像呀&#xff0c;什么再也不吃炸鸡啤酒啦&#xff…

图书管理系统jsp代码_【程序源代码】使用Java开发的图书管理系统

关键字&#xff1a;java 管理系统 正文 | 内容01—【概述】使用Java开发的图书管理系统&#xff0c;读者可以注册登录&#xff0c;登录时会判断账号类型再分别跳到各自对应的页面&#xff0c;读者可以查找&#xff0c;借阅&#xff0c;还书&#xff0c;查看历史借阅记录&#x…

老人言:尽量用异步

官方和老人言&#xff0c;asp.net core中尽量用异步&#xff0c;为什么呢&#xff1f;接下来是个小demo&#xff0c;看看同步异步的差别吧&#xff0c;或许通过这个demo&#xff0c;就明白官方和老人的良苦用心了。1、创建一个sql server的表CREATE TABLE [dbo].[Students]([St…

mysql函数保留小数_MySql自定义函数-关于保留小数位的特殊需求

背景昨天&#xff0c;关于价格详情接口又来了一个小需求&#xff0c;而且有点特别。价格显示&#xff1a;改为保留两位小数&#xff0c;没错&#xff0c;就是保留两位小数。大家是不是想说这没啥特别的。。。数据库都有函数搞定了。例如四舍五入的ROUND(x,d)&#xff0c;直接截…

整合.NET WebAPI和 Vuejs——在.NET单体应用中使用 Vuejs 和 ElementUI

.NET简介.NET 是一种用于构建多种应用的免费开源开发平台&#xff0c;例如&#xff1a;Web 应用、Web API 和微服务云中的无服务器函数云原生应用移动应用桌面应用1). Windows WPF2). Windows 窗体3). 通用 Windows 平台 (UWP)游戏物联网 (IoT)机器学习控制台应用Windows 服务跨…

python 进程池_python进程池

当需要创建的子进程数量不多时&#xff0c;可以直接利用multiprocessing中的Process动态成生多个进程&#xff0c;但如果是上百甚至上千个目标&#xff0c;手动的去创建进程的工作量巨大&#xff0c;此时就可以用到multiprocessing模块提供的Pool方法。初始化Pool时&#xff0c…

【gRPC】 在.Net core中使用gRPC

最近在学习.net core的微服务体系架构。微服务之间的通信常常通过gRPC进行同步通信&#xff0c;但是需要注意的是&#xff0c;大多数微服务之间的通信是通过事件总线进行异步通信。在微软介绍.net微服务体系架构的项目eShop中&#xff0c;微服务之间进行同步通信的场景很多&…

disconf mysql_Docker搭建disconf环境,三部曲之三:细说搭建过程

Docker下的disconf实战全文链接细说搭建过程在前两章中&#xff0c;我们利用远程或本地的镜像&#xff0c;快速体验了本地启动disconf的过程&#xff0c;本章我们一起来分析和梳理整个定制和搭建过程&#xff0c;了解这些后&#xff0c;我们就能根据自己的需要来定制本地的disc…

AgileConfig-如何使用AgileConfig.Client读取配置

前面的文章都是介绍AgileConfig服务端已经控制台是如何工作、如何使用的&#xff0c;其实AgileConfig还有一个重要的组成部分&#xff1a;AgileConfig.Client。AgileConfig.Client是使用C#编写的一个类库&#xff0c;只有使用它才能跟AgileConfig的服务端配合工作实现实时推送配…

mysql导入数据提前修改字段_Mysql一些导入导出数据库,添加修改字段命令_MySQL...

bitsCN.comMysql 导出数据库表包含数据命令&#xff1a;mysqldump -uroot -proot abc >/var/abc.sql;(mysql用户名&#xff1a;root 密码root 数据库&#xff1a;abc 导出到&#xff1a;/var/abc.sql)Mysql 导出数据库表不包含数据命令&#xff1a;mysqldump -uroot -proot …

轻量级 Kubernetes K3s - Github热点

轻量级 Kubernetes k3sstar: 15.5kK3s是完全符合生产要求的Kubernetes发行版, 安装简单&#xff0c;可用于生产&#xff0c;整个二进制文件小于100M&#xff0c;作为单一文件打包部署&#xff0c;优势在于&#xff0c;你只需几秒钟就可以得到一个完全成熟的Kubernetes集群。htt…

java 固定长度队列_如何彻底搞懂 Java 数据结构?|CSDN 博文精选

作者 | 张振华.Jack责编 | 郭芮出品 | CSDN 博客本文和大家一起来重温《Java数据结构》经典之作。Java数据结构要理解Java数据结构&#xff0c;必须能清楚何为数据结构&#xff1f;数据结构&#xff1a;Data_Structure&#xff0c;它是储存数据的一种结构体&#xff0c;在此结构…

IdentityServer4 之 Resource Owner Password Credentials 其实有点尴尬

前言接着IdentityServer4的授权模式继续聊&#xff0c;这篇来说说 Resource Owner Password Credentials授权模式&#xff0c;这种模式在实际应用场景中使用的并不多&#xff0c;只怪其太开放啦&#xff0c;直接在客户端上拿着用户名和密码就去授权服务器获取AccessToken&#…

存储过程mysql报错1271_mysqldump备份失败以及解决方法汇总

mysqldump备份失败以及解决方法汇总〇 mysqldump: Error: Query execution was interrupted, maximum statement execution time exceeded when trying to dump tablespaces〇 mysqldump: Error 3024: Query execution was interrupted, maximum statement execution time exce…

Xamarin使XRPC实现接口/委托远程调用

在之前的文章中已经介绍如何使用Beetlex.XRCP组件进行接口/委托远程调用&#xff1b;由于组件BeetleX.XRPC.Clients支持.NETStandard2&#xff0c;因此Xamarin同样可以使用它来实现基于接口/委托的数据交互通讯。接下来通过Xamarin实现一个简单的移动程序&#xff0c;并通过XRP…

mysql 拷贝安装_Mysql的安装和主从复制

安装mysql服务步骤一&#xff1a;首先下载mysql的yum源配置 &#xff0c;下载mysql的yum源wget http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm步骤二&#xff1a;安装mysql的yum源yum -y install mysql57-community-release-el7-11.noarch.rpm步骤三&…

浅谈CLR基础知识

中午的时候&#xff0c;有一个小伙伴问我&#xff0c;CLR到底是进程还是线程&#xff0c;它和自己写的程序是怎么关联的。这一问&#xff0c;直接把我问懞了。下面我尝试用简单的语言来描述这个问题&#xff0c;有的地方无法讲的太细&#xff08;不然内容会太多&#xff09;&am…