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

前言

当你想写一个泛型 <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 安装脚本将以上过程全自动化完成。

参考资料

总结

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

(0)

相关推荐

  • C#泛型和反射实例解析

    C#中的泛型和反射经常是一起工作的,因此这里就一次性的加以介绍了. 由于c#是强类型语言,一般来说函数的返回类型和参数的类型都是一早写好的,这也就造成了很多时候不像js那样方便使用,不够灵话. 因此就有了这个泛型,它可以让你的函数和参数在调用的时候才决定类型.如下例所示: public T abc<T>(T word) { return word; return default(T); //关键字default可以对引用类型返回nullAble,int类型返回0,初始化一个T的感觉啦 } ab

  • c#自定义泛型类的实现

    闲来无事,自己研究了泛型类的简单的使用,where表示泛型约束,表示泛型类型中的参数只能是car类型,IEnumerable是一个接口,一个集合要支持FOREAch遍历,必须实现IEnumerable接口 复制代码 代码如下: public class Car    {        public string PetName;        public int Speed;        public Car(string name, int currentSpeed)        {  

  • C#泛型实例详解

    本文以实例形式讲述了C#泛型的用法,有助于读者深入理解C#泛型的原理,具体分析如下: 首先需要明白什么时候使用泛型: 当针对不同的数据类型,采用相似的逻辑算法,为了避免重复,可以考虑使用泛型. 一.针对类的泛型 针对不同类型的数组,写一个针对数组的"冒泡排序". 1.思路 ● 针对类的泛型,泛型打在类旁. ● 由于在"冒泡排序"中需要对元素进行比较,所以泛型要约束成实现IComparable接口. class Program { static void Main(s

  • 浅谈c# 泛型类的应用

    泛型类泛型类封装不是特定于具体数据类型的操作. 泛型类最常用于集合,如链接列表.哈希表.堆栈.队列.树等. 像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关.对大多集合类的操作,推荐使用 .NET Framework 类库中所提供的类.(1)泛型类可以继承具体类.封闭式构造.开放式构造基类. 复制代码 代码如下: class BaseNode { }class BaseNodeGeneric<T> { }// 继承具体类class NodeConcrete<

  • 关于C#泛型列表List<T>的基本用法总结

    示例代码如下:namespace SampleListT{  class Program  {      static void Main(string[] args)      {//using System.Collections.Generic; 命名空间中的List<T>//using System.Collections; 命名空间中的ArrayList  //都实现了列表集合,一个是泛形集合,一个是非泛型的//下面我们将Person对象加到集合中 Person p1 = new P

  • C# 泛型类(函数)的实例化小例子

    泛型可以用于类,也可以用于函数.如 泛型类: 复制代码 代码如下: public class MyClass<T> {     public T MyElement { get; set; } } 泛型函数: 复制代码 代码如下: public T ReturnElement<T>() {     throw new NotImplementedException(); } 但是当需要对MyElement进行实例化的时候,却不能使用new(),只要添加如下代码即可进行实例化了: 泛

  • C#泛型约束的深入理解

    where 子句用于指定类型约束,这些约束可以作为泛型声明中定义的类型参数的变量.1.接口约束.例如,可以声明一个泛型类 MyGenericClass,这样,类型参数 T 就可以实现 IComparable<T> 接口: 复制代码 代码如下: public class MyGenericClass<T> where T:IComparable { } 2.基类约束:指出某个类型必须将指定的类作为基类(或者就是该类本身),才能用作该泛型类型的类型参数.这样的约束一经使用,就必须出现在

  • C# 泛型深入理解介绍

    引言: 在上一个专题中介绍了C#2.0 中引入泛型的原因以及有了泛型后所带来的好处,然而上一专题相当于是介绍了泛型的一些基本知识的,对于泛型的性能为什么会比非泛型的性能高却没有给出理由,所以在这个专题就中将会介绍原因和一些关于泛型的其他知识. 一.泛型类型和类型参数 泛型类型和其他int,string一样都是一种类型,泛型类型有两种表现形式的:泛型类型(包括类.接口.委托和结构,但是没有泛型枚举的)和泛型方法.那什么样的类.接口.委托和方法才称作泛型类型的呢 ?我的理解是类.接口.委托.结构或方

  • C# 泛型数组学习小结

    C# 泛型和数组在 C# 2.0 中,下限为零的一维数组自动实现 IList<T>.这使您可以创建能够使用相同代码循环访问数组和其他集合类型的泛型方法.此技术主要对读取集合中的数据很有用.IList<T> 接口不能用于在数组中添加或移除元素:如果试图在此上下文中调用 IList<T> 方法(如数组的 RemoveAt),将引发异常.下面的代码示例演示带有 IList<T> 输入参数的单个泛型方法如何同时循环访问列表和数组,本例中为整数数组. C# 泛型和数组

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

    前言 当你想写一个泛型 <T> 的类型的时候,是否想过两个泛型参数.三个泛型参数.四个泛型参数或更多泛型参数的版本如何编写呢?是一个个编写?类小还好,类大了就杯具! 事实上,在 Visual Studio 中生成代码的手段很多,本文采用最笨的方式生成,但效果也很明显--代码写得轻松写得爽! 本文主要给大家介绍了关于从T到T1.T2.Tn自动生成多个类型的泛型的方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 我们想要的效果 我们现在有一个泛型的版本: public clas

  • Session过期后自动跳转到登录页面的实例代码

    最近做了一个项目其中有需求,要实现自动登录功能,通过查阅相关资料,打算用session监听来做,下面给大家列出了配置监听器的方法: 1.在项目的web.xml文件中添加如下代码: <!--添加Session监听器--> <listener> <listener-class> 监听器路径 </listener-class> </listener> 2.编写java类. public class SessionListener implements

  • c语言实现输入一组数自动从大到小排列的实例代码

    如下所示: #include <stdio.h> main() { int x; printf("请输入要排序数字个数:"); scanf("%d",&x); int i,j,k,a,b,num[x]; printf("输入数据:"); for(i=0;i<x;i++) scanf("%d",&num[i]); for(j=0;j<x;j++) { for(k=j+1;k<x;k+

  • Android手机号码输入框(满11位自动跳到下个输入框)实例代码

    废话不多说了,直接给大家贴代码了,具体代码如下所示: package com.jixiong.teen.view; import android.content.Context; import android.text.Editable; import android.text.Selection; import android.text.TextWatcher; import android.util.AttributeSet; import android.widget.EditText;

  • python自动结束mysql慢查询会话的实例代码

    生产环境的有些sql查询写得太复杂,或是表很大,对应索引未建立或建立不合理,或是查询未充分使用索引等,就有可能出现慢查询,一些慢查询需要修改程序,可能没那么快能解决,这时如果有个脚本能自动检测符合条件的慢查询会话并结束,那么是很方便的,当然运维人员也可顺便弄个检测慢查询并告警的脚本. 涉及知识点 mysql慢查询会话查询 schedule定时任务调度 pymysql执行sql 代码分解 mysql慢查询 #会话查询,只能查询所有会话,不能按条件过滤,不过比较好记 show PROCESSLIST

  • python自动分箱,计算woe,iv的实例代码

    笔者之前用R开发评分卡时,需要进行分箱计算woe及iv值,采用的R包是smbinning,它可以自动进行分箱.近期换用python开发, 也想实现自动分箱功能,找到了一个woe包,地址https://pypi.org/project/woe/,可以直接 pip install woe安装. 由于此woe包官网介绍及给的例子不是很好理解,关于每个函数的使用也没有很详细的说明,经过一番仔细探究后以此文记录一下该woe包的使用及其计算原理. 例子 官方给的例子不是很好理解,以下是我写的一个使用示例.以

  • java判断各类型字符个数实例代码

    目录 描述 输入描述: 输出描述: 描述 输入一行字符串,分别统计出其中英文字母.空格.数字和其它字符的个数 输入描述: 控制台随机输入一串字符串 输出描述: 输出字符串中包含的英文字母个数,数字个数,空格个数,其它字符个数(格式为:英文字母x数字x空格x其他x),预设代码中已给出输出. import java.util.Scanner; public class Main { public static void main(String[] args) { int numbers = 0; i

  • Android实现自动点击无障碍服务功能的实例代码

    ps: 不想看代码的滑到最下面有apk包百度网盘下载地址 1. 先看效果图 不然都是耍流氓 2.项目目录 3.一些配置 build.gradle plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-android-extensions' } android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { applic

  • spring boot实现自动输出word文档功能的实例代码

    spring boot实现自动输出word文档功能 本文用到Apache POI组件 组件依赖在pom.xml文件中添加 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.0</version> </dependency> <dependency> <groupId&

  • 正则表达式匹配(URL、电话、手机、邮箱)的实例代码

    正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表通常被用来检索.替换那些符合某个模式(规则)的文本.下面通过实例代码给大家介绍正则表达式匹配(URL.电话.手机.邮箱)的实例代码,一起看看吧! 废话不多说了,直接给大家贴代码了,具体代码如下所示: <!DOCTYPE html> <html lang="en"> <head> <meta ch

随机推荐