Skip to content

Commit f08ec94

Browse files
committed
refactor: use UnsafeAccessorAttribute instead of reflection for .NET 8
1 parent 6503edd commit f08ec94

File tree

3 files changed

+43
-15
lines changed

3 files changed

+43
-15
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
<PropertyGroup Label="Compile settings">
2121
<Nullable>enable</Nullable>
22-
<LangVersion>11.0</LangVersion>
22+
<LangVersion>preview</LangVersion>
2323
<AccelerateBuildsInVisualStudio>false</AccelerateBuildsInVisualStudio>
2424
<ImplicitUsings>enable</ImplicitUsings>
2525
<NoWarn>CA1014,NU5104,NETSDK1138,SYSLIB0051</NoWarn>

src/.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@
3838
##########################################
3939

4040
dotnet_diagnostic.S1133.severity = none # S1133: Deprecated code should be removed
41+
dotnet_diagnostic.S3011.severity = none # S3011: Reflection should not be used to increase accessibility of classes, methods, or fields

src/bunit.core/Rendering/TestRenderer.cs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
using Microsoft.Extensions.Logging;
22
using System.Reflection;
3+
using System.Runtime.CompilerServices;
34
using System.Runtime.ExceptionServices;
45

56
namespace Bunit.Rendering;
67

78
/// <summary>
89
/// Represents a bUnit <see cref="ITestRenderer"/> used to render Blazor components and fragments during bUnit tests.
910
/// </summary>
10-
[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "Blazors internal API has been stable for a while now.")]
1111
public class TestRenderer : Renderer, ITestRenderer
1212
{
13+
#if NET8_0_OR_GREATER
14+
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_isBatchInProgress")]
15+
extern static ref bool GetIsBatchInProgressField(Renderer renderer);
16+
17+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "SetDirectParameters")]
18+
extern static void CallSetDirectParameters(ComponentState componentState, ParameterView parameters);
19+
#else
1320
private static readonly Type RendererType = typeof(Renderer);
14-
private static readonly FieldInfo IsBatchInProgressField = RendererType.GetField("_isBatchInProgress", BindingFlags.Instance | BindingFlags.NonPublic)!;
15-
#if !NET8_0_OR_GREATER
1621
private static readonly MethodInfo GetRequiredComponentStateMethod = RendererType.GetMethod("GetRequiredComponentState", BindingFlags.Instance | BindingFlags.NonPublic)!;
22+
private static readonly FieldInfo IsBatchInProgressField = RendererType.GetField("_isBatchInProgress", BindingFlags.Instance | BindingFlags.NonPublic)!;
1723
#endif
1824

1925
private readonly object renderTreeUpdateLock = new();
@@ -28,9 +34,23 @@ public class TestRenderer : Renderer, ITestRenderer
2834
private bool IsBatchInProgress
2935
{
3036
#pragma warning disable S1144 // Unused private types or members should be removed
31-
get => (bool)(IsBatchInProgressField.GetValue(this) ?? false);
37+
get
38+
{
39+
#if NET8_0_OR_GREATER
40+
return GetIsBatchInProgressField(this);
41+
#else
42+
return (bool)(IsBatchInProgressField.GetValue(this) ?? false);
43+
#endif
44+
}
3245
#pragma warning restore S1144 // Unused private types or members should be removed
33-
set => IsBatchInProgressField.SetValue(this, value);
46+
set
47+
{
48+
#if NET8_0_OR_GREATER
49+
GetIsBatchInProgressField(this) = value;
50+
#else
51+
IsBatchInProgressField.SetValue(this, value);
52+
#endif
53+
}
3454
}
3555

3656
/// <inheritdoc/>
@@ -217,15 +237,7 @@ internal Task SetDirectParametersAsync(IRenderedFragmentBase renderedComponent,
217237
try
218238
{
219239
IsBatchInProgress = true;
220-
221-
#if NET8_0_OR_GREATER
222-
var setDirectParametersMethod = typeof(ComponentState).GetMethod("SetDirectParameters", BindingFlags.NonPublic | BindingFlags.Instance)!;
223-
var componentState = GetComponentState(renderedComponent.ComponentId);
224-
#else
225-
var componentState = GetRequiredComponentStateMethod.Invoke(this, new object[] { renderedComponent.ComponentId })!;
226-
var setDirectParametersMethod = componentState.GetType().GetMethod("SetDirectParameters", BindingFlags.Public | BindingFlags.Instance)!;
227-
#endif
228-
setDirectParametersMethod.Invoke(componentState, new object[] { parameters });
240+
SetDirectParametersViaComponentState(this, renderedComponent.ComponentId, parameters);
229241
}
230242
catch (TargetInvocationException ex) when (ex.InnerException is not null)
231243
{
@@ -247,6 +259,21 @@ internal Task SetDirectParametersAsync(IRenderedFragmentBase renderedComponent,
247259
AssertNoUnhandledExceptions();
248260

249261
return result;
262+
263+
#if NET8_0_OR_GREATER
264+
static void SetDirectParametersViaComponentState(TestRenderer renderer, int componentId, in ParameterView parameters)
265+
{
266+
var componentState = renderer.GetComponentState(componentId);
267+
CallSetDirectParameters(componentState, parameters);
268+
}
269+
#else
270+
static void SetDirectParametersViaComponentState(TestRenderer renderer, int componentId, in ParameterView parameters)
271+
{
272+
var componentState = GetRequiredComponentStateMethod.Invoke(renderer, new object[] { componentId })!;
273+
var setDirectParametersMethod = componentState.GetType().GetMethod("SetDirectParameters", BindingFlags.Public | BindingFlags.Instance)!;
274+
setDirectParametersMethod.Invoke(componentState, new object[] { parameters });
275+
}
276+
#endif
250277
}
251278

252279
/// <inheritdoc/>

0 commit comments

Comments
 (0)