.net8:拦截器Interceptors

在C#12中,引入了拦截器,但一直是试验性的功能,所以自己初步看了一下,没有写文章,最近在看AOT时,发现Dapper.AOT已经用上了这个功能,觉得还是整理一下,分享出来吧,如果以后这个功能改变了,或移除了,请无视这篇文章。

下面是微软官方文档的提示:

拦截器是一项试验性功能,在 C# 12 的预览模式下提供。在将来的版本中,该功能可能会发生中断性变更或被删除。因此,不建议将其用于生产或已发布的应用程序。

其实拦截器实现比较简单,就是定义一个扩展方法,替换掉别的方法,别的方法是通过InterceptsLocation来指定的,只要保证扩展方法和原方法签名一至即可。这里要注意一点,就是需要在项目中定义一下InterceptsLocationAttribute,代码如下:

using System.Runtime.CompilerServices;


var myclass = new MyClass();
myclass.Print("测试");
myclass.Print("测试");




public class MyClass
{
    public void Print(string s)
    {
        Console.WriteLine($"MyClass.Print({s})");
    }
}
namespace Interceptors
{
    public static class MyClassIntercepts
    {
        [InterceptsLocation("C:\\MyFile\\Source\\Repos\\Asp.NetCoreExperiment\\Asp.NetCoreExperiment\\Interceptors\\InterceptorsDemo\\Program.cs", 5, 9)]
        public static void InterceptorPrint(this MyClass myclass, string s)
        {
            Console.WriteLine($"ABC.AOT下 MyClass.InterceptorPrint 拦截 MyClass.Print方法,参数是:{s}");
        }
    }
}


namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class InterceptsLocationAttribute(string filePath, int line, int column) : Attribute
    {
    }
}

如果想用拦截器,需要在项目文件(.csproj)中添加InterceptorsPreviewNamespaces,显示说明拦截器的信息。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Interceptors</InterceptorsPreviewNamespaces>
  </PropertyGroup>
</Project>

本来拦载器相对简单,就是在编译时作替换即可,这种用法自然使我想起了C#源生成器。什么是源生成器?来看一下一个例子。

首先是一个源生成器的项目TypeMessageGenerator,需要修改一下项目类型和引入Nuget包如下:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>12.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
  </ItemGroup>
</Project>

代码实现相对简单,本例就是生成项目中的一些类,类成员等信息,放在外部的一个文件中。

using Microsoft.CodeAnalysis;


namespace TypeMessageGenerator
{
    [Generator]
    public class TypeMembersSourceGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
            var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);


            var path = "C:\\MyFile\\abc\\typemembers.txt";
            File.WriteAllText(path, "");          
            File.AppendAllText(path, context.Compilation.Assembly.Name + "\r\n");
            File.AppendAllText(path, "========================\r\n");


            foreach (var typename in context.Compilation.Assembly.TypeNames)
            {
                File.AppendAllText(path, typename + "\r\n");
                try
                {
                    var mytype = context.Compilation.Assembly.GetTypeByMetadataName(typename);
                    if (mytype == null)
                    {
                        mytype = context.Compilation.Assembly.GetTypeByMetadataName($"{context.Compilation.Assembly.Name}.{typename}");
                    }
                    if (mytype == null)
                    {
                        continue;
                    }


                    foreach (var member in mytype.GetMembers())
                    {
                        try
                        {


                            File.AppendAllText(path, $"-- {member.Name} {member.Kind} \r\n");
                            foreach (var location in member.Locations)
                            {
                                try
                                {


                                    File.AppendAllText(path, $"---- {location.GetLineSpan().StartLinePosition.Line},{location.GetLineSpan().EndLinePosition.Character} {location.GetLineSpan().Path}\r\n");
                                }
                                catch (Exception exc)
                                {
                                    File.AppendAllText(path, $"1   {exc.Message} \r\n");
                                }
                            }
                        }
                        catch (Exception exc)
                        {
                            File.AppendAllText(path, $"2   {exc.Message} \r\n");
                        }
                    }
                }
                catch (Exception exc)
                {
                    File.AppendAllText(path, $"3   {exc.Message} \r\n");
                }
            }
        }


        public void Initialize(GeneratorInitializationContext context)
        {


        }
    }
}

怎么使用呢?这里定义了一个控制台项目,添加项目引用,选择上面的TypeMessageGenerator项目,并增加 OutputItemType="Analyzer" ReferenceOutputAssembly="false"这两个属性。

<Project Sdk="Microsoft.NET.Sdk">


  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  </PropertyGroup>


  <ItemGroup>
    <ProjectReference Include="..\TypeMessageGenerator\TypeMessageGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  </ItemGroup>


</Project>

在控制台项目中添加一个Person.cs文件,内容如下:

namespace UserDemoTypeMessageGenerator
{
    public class Person
    {
        public int ID { get; set; }


        public int Name { get; set; }


        public void PrintPerson()
        {
        }
    }
}

在Program.cs中不用动,这时只需要生成项目,就会发现在C:\\MyFile\\abc下生成了typemembers.txt,具体内空如下:

UserDemoTypeMessageGenerator
========================
Program
-- <Main>$ Method 
---- 0,0 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Program.cs
-- .ctor Method 
---- 0,7 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Program.cs
Person
-- <ID>k__BackingField Field 
---- 4,21 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- ID Property 
---- 4,21 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- get_ID Method 
---- 4,27 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- set_ID Method 
---- 4,32 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- <Name>k__BackingField Field 
---- 6,23 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- Name Property 
---- 6,23 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- get_Name Method 
---- 6,29 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- set_Name Method 
---- 6,34 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- PrintPerson Method 
---- 8,31 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs
-- .ctor Method 
---- 2,23 C:\MyFile\Source\Repos\Asp.NetCoreExperiment\Asp.NetCoreExperiment\SourceGenerator\UserDemoTypeMessageGenerator\Person.cs

这时要他细看,生成的这个Txt中----后,两个数字,一个路径,是不是和拦截器一样,这时你有没有一些想法?

-

技术群:添加小编微信并备注进群

小编微信:mm1552923   

公众号:dotNet编程大全    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值