溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

在Visual Studio中如何生成代碼從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型

發(fā)布時(shí)間:2021-07-22 11:20:53 來源:億速云 閱讀:176 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)在Visual Studio中如何生成代碼從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

我們想要的效果

我們現(xiàn)在有一個(gè)泛型的版本:

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)
 {
  // 做某些事情。
 }

 // 做其他事情。
}

希望生成多個(gè)泛型的版本:

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)
 {
  // 做某些事情。
 }

 // 做其他事情。
}

注意到類型的泛型變成了多個(gè),參數(shù)從一個(gè)變成了多個(gè),返回值從單個(gè)值變成了元組。

于是,怎么生成呢?

回顧 Visual Studio 那些生成代碼的方式

Visual Studio 原生自帶兩種代碼生成方式。

第一種:T4 文本模板

事實(shí)上 T4 模板算是 Visual Studio 最推薦的方式了,因?yàn)槟阒恍枰帉懸粋€(gè)包含占位符的模板文件,Visual Studio 就會(huì)自動(dòng)為你填充那些占位符。

那么 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. 
}

這代碼寫哪兒呢?在項(xiàng)目上右鍵新建項(xiàng),然后選擇“運(yùn)行時(shí)文本模板”。

在Visual Studio中如何生成代碼從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型

T4 模板編輯后一旦保存(Ctrl+S),代碼立刻生成。

有沒有覺得這代碼著色很恐怖?呃……根本就沒有代碼著色好嗎!即便如此,T4 本身也是非常強(qiáng)悍的代碼生成方式。

這不是本文的重點(diǎn),于是感興趣請(qǐng)閱讀官方文檔 Code Generation and T4 Text Templates - Microsoft Docs 學(xué)習(xí)。

第二種:文件屬性中的自定義工具

右鍵選擇項(xiàng)目中的一個(gè)代碼文件,然后選擇“屬性”,你將看到以下內(nèi)容:

在Visual Studio中如何生成代碼從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型

就是這里的自定義工具。在這里填寫工具的 Key,那么一旦這個(gè)文件保存,就會(huì)運(yùn)行自定義工具生成代碼。

那么 Key 從哪里來?這貨居然是從注冊(cè)表拿的!也就是說,如果要在團(tuán)隊(duì)使用,還需要寫一個(gè)注冊(cè)表項(xiàng)!即便如此,自定義工具本身也是非常強(qiáng)悍的代碼生成方式。

這也不是本文的重點(diǎn),于是感興趣請(qǐng)閱讀官方文檔 Custom Tools - Microsoft Docs 學(xué)習(xí)。

第三種:笨笨的編譯生成事件

這算是通常項(xiàng)目用得最多的方式了,因?yàn)樗梢栽诓恍薷挠脩糸_發(fā)環(huán)境的情況下執(zhí)行幾乎任何任務(wù)。

右鍵項(xiàng)目,選擇屬性,進(jìn)入“生成事件”標(biāo)簽:

在Visual Studio中如何生成代碼從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型

在“預(yù)先生成事件命令行”中填入工具的名字和參數(shù),便可以生成代碼。

制作生成泛型代碼的工具

我們新建一個(gè)控制臺(tái)項(xiàng)目,取名為 CodeGenerator,然后把我寫好的生成代碼粘貼到新的類文件中。

using System;
using System.Linq;
using static System.Environment;

namespace Walterlv.BuildTools
{
 public class GenericTypeGenerator
 {
  private static readonly string GeneratedHeader =
$@"//------------------------------------------------------------------------------
// <auto-generated>
//  此代碼由工具生成。
//  運(yùn)行時(shí)版本:{Environment.Version.ToString(4)}
//
//  對(duì)此文件的更改可能會(huì)導(dǎo)致不正確的行為,并且如果
//  重新生成代碼,這些更改將會(huì)丟失。
// </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()))));
  }
 }
}

這個(gè)類中加入了非常多種常見的泛型字符串特征,當(dāng)然是采用最笨的字符串替換方法。如果感興趣優(yōu)化優(yōu)化,可以用正則表達(dá)式,或者使用 Roslyn 擴(kuò)展直接拿語法樹。

于是,在 Program.cs 中調(diào)用以上代碼即可完成泛型生成。我寫了一個(gè)簡(jiǎn)單的版本,可以將每一個(gè)命令行參數(shù)解析為一個(gè)需要進(jìn)行轉(zhuǎn)換的泛型類文件。

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)
 {
  // 讀取原始文件并創(chuàng)建泛型代碼生成器。
  var template = File.ReadAllText(file, Encoding.UTF8);
  var generator = new GenericTypeGenerator(template);

  // 根據(jù)泛型個(gè)數(shù)生成目標(biāo)文件路徑和文件內(nèi)容。
  var format = GetIndexedFileNameFormat(file);
  (string targetFileName, string targetFileContent)[] contents = Enumerable.Range(2, count - 1).Select(i =>
   (string.Format(format, i), generator.Generate(i))
  ).ToArray();

  // 寫入目標(biāo)文件。
  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 級(jí)別的代碼,我將生成的泛型個(gè)數(shù)直接寫到了代碼當(dāng)中。這段代碼的意思是按文件名遞增生成多個(gè)泛型類。

例如,有一個(gè)泛型類文件 Demo.cs,則會(huì)在同目錄生成 Demo2.cs,Demo3.cs,Demo4.cs。當(dāng)然,Demo.cs 命名為 Demo1.cs 結(jié)果也是一樣的。

在要生成代碼的項(xiàng)目中添加“預(yù)先生成事件命令行”:

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

現(xiàn)在,編譯此項(xiàng)目,即可生成多個(gè)泛型類了。

感謝各位的閱讀!關(guān)于“在Visual Studio中如何生成代碼從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI