问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

泛型类的代码

发布网友 发布时间:2022-05-19 09:19

我来回答

2个回答

懂视网 时间:2022-05-19 13:40

前言

当你想写一个泛型 <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)net47CodeGenerator.exe" "$(ProjectDir)..Walterlv.DemoGenericIDemoFile.cs" "$(ProjectDir)....Walterlv.DemoGenericDemoFile.cs" 

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

彩蛋

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

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

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

参考资料

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

    热心网友 时间:2022-05-19 10:48

    Public Class Generic(Of T)
    Public Field As T
    End Class
    C# 代码
    public class Generic<T>
    {
    public T Field;
    }
    C++ 代码
    generic<typename T> public ref class Generic
    {
    public:
    T Field;
    };
    在创建泛型类的实例时,会指定实际类型来替换类型参数。这会建立一个新的泛型类,称为构造泛型类,选定的类型将替换所有出现的类型参数。最后得到按照您选择的类型定制的类型安全的类,如下面的代码所示。
    Visual Basic 代码
    Dim g As New Generic(Of String)
    g.Field = A string
    C# 代码
    Generic<string> g = new Generic<string>();
    g.Field = A string;
    C++ 代码
    Generic<String^>^ g = gcnew Generic<String^>();
    g->Field = A string;

    声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
    橙子冷藏能保存多久 橙子会过期吗 新鲜橙子如何保存更久 橙子保鲜剂对人体有害吗 粒上皇开口熟栗120g*5袋(共600g)-详细介绍 三皇王板栗介绍 品牌榜:2024年板栗十大品牌排行榜 投票结果公布【新】 为什么来大姨妈胸会胀 少儿学什么舞蹈 青年学什么舞蹈好 登机前机票丢了怎么办? O型血金牛座男生很花心吗? 我提前购买的机票但是机票丢了不记得时间了怎么办 金牛座容易遇到渣男吗? 金牛男为什么都那么渣 机票丢了 怎么办 无缝衔接,特别渣的星座男有哪些,你知道吗? 支付宝8.9版本下载 机票取完了丢了怎么办 请问金牛座的男生会比较主动吗,而且他对自己喜欢的女生会是怎样的... 金牛男很渣 金牛座很容易出渣男吗 金牛座渣吗? 每个星座都有渣男和渣女,金牛座的渣男到底渣在哪? 跟金牛最不配的是哪个星座?金牛男渣吗? 6级听力一题几分啊? 诺莱维褪黑色素b6是正品吗? 哪个牌子的褪黑素好 什么有臭鸡蛋气味 计提短期借款利息,做会计分录 什么牙膏可以长期使用啊?换来换去还挺费时间的…. 牙膏用什么牌子的好? 经常上火用什么牌子的牙膏好? 牙齿敏感用什么牙膏好?之前一直用舒适达,现在想换个牌子用,打工人不要太贵的,能长期使用就好! 请问冰箱一直嗡嗡响是怎么回事 wps25周年典藏版皮肤 怎么用 关于WPS Office的皮肤 请问各位大侠,wps可以换皮肤的是哪个版本啊,我以前的好像可以,后来提示更新,更新成2102版就没了,谢了 空气压缩储存对人类的好处? 六大营养物质当中能起到储存的作用是哪一个? 对子公司持股比例和表决权比例不一致时如何进行报表合并 如何用ios的蜂窝数据连接代理服务器上网 换了个新iphone,有一个APP应用在apple store上暂时下架了,有什么办法能把我老ip 莱卡相机的昔日辉煌 哪位知道莱卡相机的历史? 老式莱卡相机天价是怎么拍出来的 莱卡相机为什么能成为相机中的贵族? 徕卡情结怎么样 什么叫莱卡相机 男职工缴生育险有什么用?