-
Notifications
You must be signed in to change notification settings - Fork 1
Home
Igor Tkachev edited this page Dec 8, 2023
·
27 revisions
Let's create a new project and see step by step how it works.
AspectTest.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Program.cs:
A.InterceptableMethod();
static class A
{
public static void InterceptableMethod()
{
// This method just prints "interceptable" to console.
//
Console.WriteLine("interceptable");
}
}
Run it and see output:
interceptable
Now let's add InterceptsLocation
attribute to our code and intercept call to InterceptableMethod
.
Modified project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<-- Add this line -->
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Interceptors</InterceptorsPreviewNamespaces>
</PropertyGroup>
</Project>
Program.cs:
A.InterceptableMethod();
static class A
{
public static void InterceptableMethod()
{
Console.WriteLine("interceptable");
}
}
namespace Interceptors
{
using System.Runtime.CompilerServices;
class B
{
// This method will be called instead of `InterceptableMethod`.
// 'InterceptsLocation' attribute tells compiler to replace call to `InterceptableMethod` with call to `InterceptorMethod`.
//
[InterceptsLocation(@"P:\Test\AspectTest\Program.cs", line: 1, character: 3)]
public static void InterceptorMethod()
{
Console.WriteLine("interceptor");
}
}
}
// For now we have to define `InterceptsLocation` attribute ourselves.
//
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute
{
}
}
Now we see different output:
interceptor
Note
When compiler sees InterceptsLocation
attribute, it will replace call to InterceptableMethod
with call to InterceptorMethod.
InterceptsLocation
is design to be used by source generators only. It takes absolute path to file, exact line and character position that makes direct use of it practically very hard absolutely impossible.
AspectGenerator (AG) is one of such source generators.
Modified project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);AspectGenerator</InterceptorsPreviewNamespaces>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspectGenerator" Version="0.0.5-preview" />
</ItemGroup>
</Project>
Program.cs:
A.InterceptableMethod();
static class A
{
[Aspects.Intercept]
public static void InterceptableMethod()
{
Console.WriteLine("interceptable");
}
}
namespace Aspects
{
using AspectGenerator;
[Aspect(
OnBeforeCall = nameof(OnBeforeCall)
)]
class InterceptAttribute : Attribute
{
public static void OnBeforeCall(InterceptInfo info)
{
Console.WriteLine("aspect");
info.InterceptResult = InterceptResult.Return;
}
}
}
Now we see different output:
aspect
Interceptors.g.cs:
// <auto-generated/>
#pragma warning disable
#nullable enable
using System;
using SR = System.Reflection;
using SLE = System.Linq.Expressions;
using SCG = System.Collections.Generic;
namespace AspectGenerator
{
using AspectGenerator = AspectGenerator;
static partial class Interceptors
{
static SR.MethodInfo GetMethodInfo(SLE.Expression expr)
{
return expr switch
{
SLE.MethodCallExpression mc => mc.Method,
_ => throw new InvalidOperationException()
};
}
static SR.MethodInfo MethodOf<T>(SLE.Expression<Func<T>> func) => GetMethodInfo(func.Body);
static SR.MethodInfo MethodOf (SLE.Expression<Action> func) => GetMethodInfo(func.Body);
static SR. MemberInfo InterceptableMethod_Interceptor_MemberInfo = MethodOf(() => A.InterceptableMethod());
static SCG.Dictionary<string,object?> InterceptableMethod_Interceptor_AspectArguments_0 = new()
{
};
//
/// <summary>
/// Intercepts A.InterceptableMethod().
/// </summary>
//
// Intercepts A.InterceptableMethod().
[System.Runtime.CompilerServices.InterceptsLocation(@"P:\Test\AspectTest\Program.cs", line: 1, character: 3)]
//
[System.Runtime.CompilerServices.CompilerGenerated]
//[System.Diagnostics.DebuggerStepThrough]
public static void InterceptableMethod_Interceptor()
{
// Aspects.InterceptAttribute
//
var __info__0 = new AspectGenerator.InterceptInfo<AspectGenerator.Void>
{
MemberInfo = InterceptableMethod_Interceptor_MemberInfo,
AspectType = typeof(Aspects.InterceptAttribute),
AspectArguments = InterceptableMethod_Interceptor_AspectArguments_0,
};
__info__0.InterceptType = AspectGenerator.InterceptType.OnBeforeCall;
Aspects.InterceptAttribute.OnBeforeCall(__info__0);
if (__info__0.InterceptResult != AspectGenerator.InterceptResult.Return)
{
A.InterceptableMethod();
}
}
}
}