在Visual Studio中如何生成代码从T到T1、T2、Tn自动生成多个类型的泛型

发布时间:2021-07-22 11:20:53 作者:小新
来源:亿速云 阅读:179

这篇文章给大家分享的是有关在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. 
}

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

在Visual Studio中如何生成代码从T到T1、T2、Tn自动生成多个类型的泛型

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

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

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

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

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

在Visual Studio中如何生成代码从T到T1、T2、Tn自动生成多个类型的泛型

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

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

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

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

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

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

在Visual Studio中如何生成代码从T到T1、T2、Tn自动生成多个类型的泛型

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

制作生成泛型代码的工具

我们新建一个控制台项目,取名为 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"

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

感谢各位的阅读!关于“在Visual Studio中如何生成代码从T到T1、T2、Tn自动生成多个类型的泛型”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

推荐阅读:
  1. Task类在.NET4.5中的一些改进
  2. Visual Studio 2011 Beta 下载

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

visual studio

上一篇:如何实现java公众平台通用接口工具类HttpConnectUtil

下一篇:PHP中unlink与rmdir删除目录及目录下所有文件的示例分析

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》