生成代碼從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型實(shí)例代碼
前言
當(dāng)你想寫(xiě)一個(gè)泛型 <T> 的類型的時(shí)候,是否想過(guò)兩個(gè)泛型參數(shù)、三個(gè)泛型參數(shù)、四個(gè)泛型參數(shù)或更多泛型參數(shù)的版本如何編寫(xiě)呢?是一個(gè)個(gè)編寫(xiě)?類小還好,類大了就杯具!
事實(shí)上,在 Visual Studio 中生成代碼的手段很多,本文采用最笨的方式生成,但效果也很明顯——代碼寫(xiě)得輕松寫(xiě)得爽!
本文主要給大家介紹了關(guān)于從T到T1、T2、Tn自動(dòng)生成多個(gè)類型的泛型的方法,分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧
我們想要的效果
我們現(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)槟阒恍枰帉?xiě)一個(gè)包含占位符的模板文件,Visual Studio 就會(huì)自動(dòng)為你填充那些占位符。
那么 Visual Studio 用什么填充?是的,可以在模板文件中寫(xiě) 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ě)哪兒呢?在項(xiàng)目上右鍵新建項(xiàng),然后選擇“運(yùn)行時(shí)文本模板”。

T4 模板編輯后一旦保存(Ctrl+S),代碼立刻生成。
有沒(méi)有覺(jué)得這代碼著色很恐怖?呃……根本就沒(méi)有代碼著色好嗎!即便如此,T4 本身也是非常強(qiáng)悍的代碼生成方式。
這不是本文的重點(diǎn),于是感興趣請(qǐng)閱讀官方文檔 Code Generation and T4 Text Templates - Microsoft Docs 學(xué)習(xí)。
第二種:文件屬性中的自定義工具
右鍵選擇項(xiàng)目中的一個(gè)代碼文件,然后選擇“屬性”,你將看到以下內(nèi)容:

就是這里的自定義工具。在這里填寫(xiě)工具的 Key,那么一旦這個(gè)文件保存,就會(huì)運(yùn)行自定義工具生成代碼。
那么 Key 從哪里來(lái)?這貨居然是從注冊(cè)表拿的!也就是說(shuō),如果要在團(tuán)隊(duì)使用,還需要寫(xiě)一個(gè)注冊(cè)表項(xiàng)!即便如此,自定義工具本身也是非常強(qiáng)悍的代碼生成方式。
這也不是本文的重點(diǎn),于是感興趣請(qǐng)閱讀官方文檔 Custom Tools - Microsoft Docs 學(xué)習(xí)。
第三種:笨笨的編譯生成事件
這算是通常項(xiàng)目用得最多的方式了,因?yàn)樗梢栽诓恍薷挠脩糸_(kāi)發(fā)環(huán)境的情況下執(zhí)行幾乎任何任務(wù)。
右鍵項(xiàng)目,選擇屬性,進(jìn)入“生成事件”標(biāo)簽:

在“預(yù)先生成事件命令行”中填入工具的名字和參數(shù),便可以生成代碼。
制作生成泛型代碼的工具
我們新建一個(gè)控制臺(tái)項(xiàng)目,取名為 CodeGenerator,然后把我寫(xiě)好的生成代碼粘貼到新的類文件中。
using System;
using System.Linq;
using static System.Environment;
namespace Walterlv.BuildTools
{
public class GenericTypeGenerator
{
private static readonly string GeneratedHeader =
$@"http://------------------------------------------------------------------------------
// <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è)類中加入了非常多種常見(jiàn)的泛型字符串特征,當(dāng)然是采用最笨的字符串替換方法。如果感興趣優(yōu)化優(yōu)化,可以用正則表達(dá)式,或者使用 Roslyn 擴(kuò)展直接拿語(yǔ)法樹(shù)。
于是,在 Program.cs 中調(diào)用以上代碼即可完成泛型生成。我寫(xiě)了一個(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();
// 寫(xiě)入目標(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ù)直接寫(xiě)到了代碼當(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è)泛型類了。
彩蛋
如果你仔細(xì)閱讀了 GenericTypeGenerator 類的代碼,你將注意到我為生成的文件加上了條件編譯符“GENERATED_CODE”。這樣,你便可以使用 #ifdef GENERATED_CODE 來(lái)處理部分不需要進(jìn)行轉(zhuǎn)換或轉(zhuǎn)換有差異的代碼了。
這時(shí)寫(xiě)代碼,是不是完全感受不到正在寫(xiě)模板呢?既有代碼著色,又適用于團(tuán)隊(duì)其他開(kāi)發(fā)者的開(kāi)發(fā)環(huán)境。是的,個(gè)人認(rèn)為如果帶來(lái)便捷的同時(shí)注意不到工具的存在,那么這個(gè)工具便是好的。
如果將傳參改為自動(dòng)尋找代碼文件,將此工具發(fā)布到 NuGet,那么可以通過(guò) NuGet 安裝腳本將以上過(guò)程全自動(dòng)化完成。
參考資料
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
刪除DataTable重復(fù)列,只刪除其中的一列重復(fù)行的解決方法
刪除DataTable重復(fù)列,只刪除其中的一列重復(fù)行,下面的方法就可以,也許有更好的方法,希望大家多多指教2013-02-02
.Net Core官方JWT授權(quán)驗(yàn)證的全過(guò)程
這篇文章主要給大家介紹了關(guān)于.Net Core官方JWT授權(quán)驗(yàn)證的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
IE10下Gridview后臺(tái)設(shè)置行高不起作用解決方法
GridView1.HeaderStyle.Height=17發(fā)現(xiàn)在IE10 中不起作用,經(jīng)過(guò)反復(fù)測(cè)試修改為e.Row.Cells[0].Height=17即可解決問(wèn)題,有類似問(wèn)題的朋友可以參考下哈2013-04-04
C#調(diào)用C++版本dll時(shí)的類型轉(zhuǎn)換需要注意的問(wèn)題小結(jié)
最近使用C#調(diào)用C++版本的dll遇到很多類型轉(zhuǎn)換的問(wèn)題,現(xiàn)記錄出容易出錯(cuò)的部分。2010-04-04
asp.net 動(dòng)態(tài)創(chuàng)建TextBox控件及狀態(tài)數(shù)據(jù)如何加載
接著上文Asp.net TextBox的TextChanged事件你真的清楚嗎?這里我們來(lái)說(shuō)說(shuō)狀態(tài)數(shù)據(jù)時(shí)如何加載的,需要的朋友可以參考下2012-12-12
ASP.NET頁(yè)面借助IFrame提交表單數(shù)據(jù)所遇到問(wèn)題的解決方法分享
ASP.NET頁(yè)面借助IFrame提交表單數(shù)據(jù)所遇到問(wèn)題的解決方法分享,碰到同樣問(wèn)題的朋友可以參考下。2011-10-10
使用Hangfire+.NET?6實(shí)現(xiàn)定時(shí)任務(wù)管理(推薦)
這篇文章主要介紹了使用Hangfire+.NET?6實(shí)現(xiàn)定時(shí)任務(wù)管理,通過(guò)引入Hangfire相關(guān)的Nuget包并對(duì)Hangfire進(jìn)行服務(wù)配置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10

