Skip to content

Reduce value type boxing in interop calls #787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 27, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 55 additions & 36 deletions Fluid/Accessors/PropertyInfoAccessor.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using System.Reflection;
using System.Reflection.Emit;
using Fluid.Values;

namespace Fluid.Accessors
{
public sealed class PropertyInfoAccessor : IMemberAccessor
{
private readonly IInvoker _invoker;
private readonly Invoker _invoker;

public PropertyInfoAccessor(PropertyInfo propertyInfo)
{
Delegate d;

if (!propertyInfo.DeclaringType.IsValueType)
if (!propertyInfo.DeclaringType?.IsValueType == true)
{
var delegateType = typeof(Func<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
d = propertyInfo.GetGetMethod().CreateDelegate(delegateType);
Expand All @@ -21,7 +22,7 @@ public PropertyInfoAccessor(PropertyInfo propertyInfo)
// We can't create an open delegate on a struct (dotnet limitation?), so instead create custom delegates
// https://sharplab.io/#v2:EYLgtghglgdgNAFxAJwK7wCYgNQB8ACATAAwCwAUEQIwX7EAE+VAdACLIQDusA5gNwUKANwjJ6ABwCSMAGYB7egF56CAJ7iApnJkAKAApzYCAJTMA4hoR7kczcjU6ARAA1HxgeRFiMhJROny5pYWCACylgAWchg6pgDCyBoQCBqsGgA2GjzJGjpqmto6+ACsADwGRnD0RgB8xu4UQA==
// Instead we generate IL to access the backing field directly

d = GetGetter(propertyInfo.DeclaringType, propertyInfo.Name);
}

Expand All @@ -30,58 +31,76 @@ public PropertyInfoAccessor(PropertyInfo propertyInfo)
_invoker = null;
}

var invokerType = typeof(Invoker<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
_invoker = Activator.CreateInstance(invokerType, [d]) as IInvoker;
}
Type invokerType;
if (propertyInfo.PropertyType == typeof(bool))
{
invokerType = typeof(BooleanInvoker<>).MakeGenericType(propertyInfo.DeclaringType);
}
else if (propertyInfo.PropertyType == typeof(int))
{
invokerType = typeof(Int32Invoker<>).MakeGenericType(propertyInfo.DeclaringType);
}
else
{
invokerType = typeof(Invoker<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
}

public object Get(object obj, string name, TemplateContext ctx)
{
return _invoker?.Invoke(obj);
_invoker = (Invoker) Activator.CreateInstance(invokerType, [d]);
}

public object Get(object obj, string name, TemplateContext ctx) => _invoker.Invoke(obj);

private static Delegate GetGetter(Type declaringType, string fieldName)
{
string[] names = [fieldName.ToLowerInvariant(), $"<{fieldName}>k__BackingField", "_" + fieldName.ToLowerInvariant()];

var field = names
.Select(n => declaringType.GetField(n, BindingFlags.Instance | BindingFlags.NonPublic))
.FirstOrDefault(x => x != null);
string[] names = [fieldName.ToLowerInvariant(), $"<{fieldName}>k__BackingField", $"_{fieldName.ToLowerInvariant()}"];

if (field == null)
foreach (var n in names)
{
return null;
}
var field = declaringType.GetField(n, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
continue;
}

var parameterTypes = new[] { typeof(object), declaringType };
var parameterTypes = new[] { typeof(object), declaringType };

var method = new DynamicMethod(fieldName + "Get", field.FieldType, parameterTypes, typeof(PropertyInfoAccessor).Module, true);
var method = new DynamicMethod(fieldName + "Get", field.FieldType, parameterTypes, typeof(PropertyInfoAccessor).Module, true);

var emitter = method.GetILGenerator();
emitter.Emit(OpCodes.Ldarg_1);
emitter.Emit(OpCodes.Ldfld, field);
emitter.Emit(OpCodes.Ret);
var emitter = method.GetILGenerator();
emitter.Emit(OpCodes.Ldarg_1);
emitter.Emit(OpCodes.Ldfld, field);
emitter.Emit(OpCodes.Ret);

return method.CreateDelegate(typeof(Func<,>).MakeGenericType(declaringType, field.FieldType));
return method.CreateDelegate(typeof(Func<,>).MakeGenericType(declaringType, field.FieldType));
}

return null;
}

private interface IInvoker
private abstract class Invoker
{
object Invoke(object target);
public abstract object Invoke(object target);
}

private sealed class Invoker<T, TResult> : IInvoker
private sealed class Invoker<T, TResult>(Delegate d) : Invoker
{
private readonly Func<T, TResult> _d;
private readonly Func<T, TResult> _d = (Func<T, TResult>) d;

public Invoker(Delegate d)
{
_d = (Func<T, TResult>)d;
}
public override object Invoke(object target) => _d((T) target);
}

public object Invoke(object target)
{
return _d((T)target);
}
private sealed class BooleanInvoker<T>(Delegate d) : Invoker
{
private readonly Func<T, bool> _d = (Func<T, bool>) d;

public override object Invoke(object target) => _d((T) target) ? BooleanValue.True : BooleanValue.False;
}

private sealed class Int32Invoker<T>(Delegate d) : Invoker
{
private readonly Func<T, int> _d = (Func<T, int>) d;

public override object Invoke(object target) => NumberValue.Create(_d((T) target));
}
}
}