侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

生成代码从T到T1、T2、Tn自动生成多个类型的泛型实例代码

技术  /  管理员 发布于 7年前   211

前言

当你想写一个泛型 <T> 的类型的时候,是否想过两个泛型参数、三个泛型参数、四个泛型参数或更多泛型参数的版本如何编写呢?是一个个编写?类小还好,类大了就杯具!

事实上,在 Visual Studio 中生成代码的手段很多,本文采用最笨的方式生成,但效果也很明显――代码写得轻松写得爽!

本文主要给大家介绍了关于从T到T1、T2、Tn自动生成多个类型的泛型的方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

我们想要的效果

我们现在有一个泛型的版本:

public class Demo<T>{ public Demo(Action<T> demo) {  _demo = demo ?? throw new ArgumentNullException(nameof(action)); } private Action<T> _demo; public async Task<T> DoAsync(T t) {  // 做某些事情。 } // 做其他事情。}

希望生成多个泛型的版本:

public class Demo<T1, T2>{ public Demo(Action<T1, T2> demo) {  _demo = demo ?? throw new ArgumentNullException(nameof(action)); } private Action<T1, T2> _demo; public async Task<(T1, T2)> DoAsync(T1 t1, T2 t2) {  // 做某些事情。 } // 做其他事情。}

注意到类型的泛型变成了多个,参数从一个变成了多个,返回值从单个值变成了元组。

于是,怎么生成呢?

回顾 Visual Studio 那些生成代码的方式

Visual Studio 原生自带两种代码生成方式。

第一种:T4 文本模板

事实上 T4 模板算是 Visual Studio 最推荐的方式了,因为你只需要编写一个包含占位符的模板文件,Visual Studio 就会自动为你填充那些占位符。

那么 Visual Studio 用什么填充?是的,可以在模板文件中写 C# 代码!比如官方 DEMO:

<#@ output extension=".txt" #> <#@ assembly name="System.Xml" #> <#  System.Xml.XmlDocument configurationData = ...; // Read a data file here. #> namespace Fabrikam.<#= configurationData.SelectSingleNode("jobName").Value #> {  ... // More code here. } 

这代码写哪儿呢?在项目上右键新建项,然后选择“运行时文本模板”。

T4 模板编辑后一旦保存(Ctrl+S),代码立刻生成。

有没有觉得这代码着色很恐怖?呃……根本就没有代码着色好吗!即便如此,T4 本身也是非常强悍的代码生成方式。

这不是本文的重点,于是感兴趣请阅读官方文档 Code Generation and T4 Text Templates - Microsoft Docs 学习。

第二种:文件属性中的自定义工具

右键选择项目中的一个代码文件,然后选择“属性”,你将看到以下内容:

就是这里的自定义工具。在这里填写工具的 Key,那么一旦这个文件保存,就会运行自定义工具生成代码。

那么 Key 从哪里来?这货居然是从注册表拿的!也就是说,如果要在团队使用,还需要写一个注册表项!即便如此,自定义工具本身也是非常强悍的代码生成方式。

这也不是本文的重点,于是感兴趣请阅读官方文档 Custom Tools - Microsoft Docs 学习。

第三种:笨笨的编译生成事件

这算是通常项目用得最多的方式了,因为它可以在不修改用户开发环境的情况下执行几乎任何任务。

右键项目,选择属性,进入“生成事件”标签:

在“预先生成事件命令行”中填入工具的名字和参数,便可以生成代码。

制作生成泛型代码的工具

我们新建一个控制台项目,取名为 CodeGenerator,然后把我写好的生成代码粘贴到新的类文件中。

using System;using System.Linq;using static System.Environment;namespace Walterlv.BuildTools{ public class GenericTypeGenerator {  private static readonly string GeneratedHeader =$@"//------------------------------------------------------------------------------// <auto-generated>//  此代码由工具生成。//  运行时版本:{Environment.Version.ToString(4)}////  对此文件的更改可能会导致不正确的行为,并且如果//  重新生成代码,这些更改将会丢失。// </auto-generated>//------------------------------------------------------------------------------#define GENERATED_CODE";  private static readonly string GeneratedFooter =   $@"";  private readonly string _genericTemplate;  private readonly string _toolName;  public GenericTypeGenerator(string toolName, string genericTemplate)  {   _toolName = toolName ?? throw new ArgumentNullException(nameof(toolName));   _genericTemplate = genericTemplate ?? throw new ArgumentNullException(nameof(toolName));  }  public string Generate(int genericCount)  {   var toolName = _toolName;   var toolVersion = "1.0";   var GeneratedAttribute = $"[System.CodeDom.Compiler.GeneratedCode(\"{toolName}\", \"{toolVersion}\")]";   var content = _genericTemplate    // 替换泛型。    .Replace("<out T>", FromTemplate("<{0}>", "out T{n}", ", ", genericCount))    .Replace("Task<T>", FromTemplate("Task<({0})>", "T{n}", ", ", genericCount))    .Replace("Func<T, Task>", FromTemplate("Func<{0}, Task>", "T{n}", ", ", genericCount))    .Replace(" T, Task>", FromTemplate(" {0}, Task>", "T{n}", ", ", genericCount))    .Replace("(T, bool", FromTemplate("({0}, bool", "T{n}", ", ", genericCount))    .Replace("var (t, ", FromTemplate("var ({0}, ", "t{n}", ", ", genericCount))    .Replace(", t)", FromTemplate(", {0})", "t{n}", ", ", genericCount))    .Replace("return (t, ", FromTemplate("return ({0}, ", "t{n}", ", ", genericCount))    .Replace("<T>", FromTemplate("<{0}>", "T{n}", ", ", genericCount))    .Replace("(T value)", FromTemplate("(({0}) value)", "T{n}", ", ", genericCount))    .Replace("(T t)", FromTemplate("({0})", "T{n} t{n}", ", ", genericCount))    .Replace("(t)", FromTemplate("({0})", "t{n}", ", ", genericCount))    .Replace("var t =", FromTemplate("var ({0}) =", "t{n}", ", ", genericCount))    .Replace(" T ", FromTemplate(" ({0}) ", "T{n}", ", ", genericCount))    .Replace(" t;", FromTemplate(" ({0});", "t{n}", ", ", genericCount))    // 生成 [GeneratedCode]。    .Replace(" public interface ", $" {GeneratedAttribute}{NewLine} public interface ")    .Replace(" public class ", $" {GeneratedAttribute}{NewLine} public class ")    .Replace(" public sealed class ", $" {GeneratedAttribute}{NewLine} public sealed class ");   return GeneratedHeader + NewLine + content.Trim() + NewLine + GeneratedFooter;  }  private static string FromTemplate(string template, string part, string separator, int count)  {   return string.Format(template,    string.Join(separator, Enumerable.Range(1, count).Select(x => part.Replace("{n}", x.ToString()))));  } }}

这个类中加入了非常多种常见的泛型字符串特征,当然是采用最笨的字符串替换方法。如果感兴趣优化优化,可以用正则表达式,或者使用 Roslyn 扩展直接拿语法树。

于是,在 Program.cs 中调用以上代码即可完成泛型生成。我写了一个简单的版本,可以将每一个命令行参数解析为一个需要进行转换的泛型类文件。

using System.IO;using System.Linq;using System.Text;using Walterlv.BuildTools;class Program{ static void Main(string[] args) {  foreach (var argument in args)  {   GenerateGenericTypes(argument, 4);  } } private static void GenerateGenericTypes(string file, int count) {  // 读取原始文件并创建泛型代码生成器。  var template = File.ReadAllText(file, Encoding.UTF8);  var generator = new GenericTypeGenerator(template);  // 根据泛型个数生成目标文件路径和文件内容。  var format = GetIndexedFileNameFormat(file);  (string targetFileName, string targetFileContent)[] contents = Enumerable.Range(2, count - 1).Select(i =>   (string.Format(format, i), generator.Generate(i))  ).ToArray();  // 写入目标文件。  foreach (var writer in contents)  {   File.WriteAllText(writer.targetFileName, writer.targetFileContent);  } } private static string GetIndexedFileNameFormat(string fileName) {  var directory = Path.GetDirectoryName(fileName);  var name = Path.GetFileNameWithoutExtension(fileName);  if (name.EndsWith("1"))  {   name = name.Substring(0, name.Length - 1);  }  return Path.Combine(directory, name + "{0}.cs"); }}

考虑到这是 Demo 级别的代码,我将生成的泛型个数直接写到了代码当中。这段代码的意思是按文件名递增生成多个泛型类。

例如,有一个泛型类文件 Demo.cs,则会在同目录生成 Demo2.cs,Demo3.cs,Demo4.cs。当然,Demo.cs 命名为 Demo1.cs 结果也是一样的。

在要生成代码的项目中添加“预先生成事件命令行”:

"$(ProjectDir)..\CodeGenerator\$(OutDir)net47\CodeGenerator.exe" "$(ProjectDir)..\Walterlv.Demo\Generic\IDemoFile.cs" "$(ProjectDir)..\..\Walterlv.Demo\Generic\DemoFile.cs" 

现在,编译此项目,即可生成多个泛型类了。

彩蛋

如果你仔细阅读了 GenericTypeGenerator 类的代码,你将注意到我为生成的文件加上了条件编译符“GENERATED_CODE”。这样,你便可以使用 #ifdef GENERATED_CODE 来处理部分不需要进行转换或转换有差异的代码了。

这时写代码,是不是完全感受不到正在写模板呢?既有代码着色,又适用于团队其他开发者的开发环境。是的,个人认为如果带来便捷的同时注意不到工具的存在,那么这个工具便是好的。

如果将传参改为自动寻找代码文件,将此工具发布到 NuGet,那么可以通过 NuGet 安装脚本将以上过程全自动化完成。

参考资料

  • Code Generation and T4 Text Templates - Microsoft Docs
  • Custom Tools - Microsoft Docs

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对AIDI的支持。


  • 上一条:
    Visual Studio Debug实战教程之断点操作
    下一条:
    VS Code 1.26 发布 有你想要的新特性?
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 2024/6/9最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(0个评论)
    • 国外服务器实现api.openai.com反代nginx配置(0个评论)
    • 2024/4/28最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客