Skip to content

Commit 9b11031

Browse files
authored
Reduce value type boxing in interop calls (#787)
* Reduce value type boxing in interop calls * special case common bool and int property access * use abstract class instead of interface do reduce virtual dispatch cost * code tweaks
1 parent d52cb55 commit 9b11031

1 file changed

Lines changed: 55 additions & 36 deletions

File tree

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
using System.Reflection;
22
using System.Reflection.Emit;
3+
using Fluid.Values;
34

45
namespace Fluid.Accessors
56
{
67
public sealed class PropertyInfoAccessor : IMemberAccessor
78
{
8-
private readonly IInvoker _invoker;
9+
private readonly Invoker _invoker;
910

1011
public PropertyInfoAccessor(PropertyInfo propertyInfo)
1112
{
1213
Delegate d;
1314

14-
if (!propertyInfo.DeclaringType.IsValueType)
15+
if (!propertyInfo.DeclaringType?.IsValueType == true)
1516
{
1617
var delegateType = typeof(Func<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
1718
d = propertyInfo.GetGetMethod().CreateDelegate(delegateType);
@@ -21,7 +22,7 @@ public PropertyInfoAccessor(PropertyInfo propertyInfo)
2122
// We can't create an open delegate on a struct (dotnet limitation?), so instead create custom delegates
2223
// https://sharplab.io/#v2:EYLgtghglgdgNAFxAJwK7wCYgNQB8ACATAAwCwAUEQIwX7EAE+VAdACLIQDusA5gNwUKANwjJ6ABwCSMAGYB7egF56CAJ7iApnJkAKAApzYCAJTMA4hoR7kczcjU6ARAA1HxgeRFiMhJROny5pYWCACylgAWchg6pgDCyBoQCBqsGgA2GjzJGjpqmto6+ACsADwGRnD0RgB8xu4UQA==
2324
// Instead we generate IL to access the backing field directly
24-
25+
2526
d = GetGetter(propertyInfo.DeclaringType, propertyInfo.Name);
2627
}
2728

@@ -30,58 +31,76 @@ public PropertyInfoAccessor(PropertyInfo propertyInfo)
3031
_invoker = null;
3132
}
3233

33-
var invokerType = typeof(Invoker<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
34-
_invoker = Activator.CreateInstance(invokerType, [d]) as IInvoker;
35-
}
34+
Type invokerType;
35+
if (propertyInfo.PropertyType == typeof(bool))
36+
{
37+
invokerType = typeof(BooleanInvoker<>).MakeGenericType(propertyInfo.DeclaringType);
38+
}
39+
else if (propertyInfo.PropertyType == typeof(int))
40+
{
41+
invokerType = typeof(Int32Invoker<>).MakeGenericType(propertyInfo.DeclaringType);
42+
}
43+
else
44+
{
45+
invokerType = typeof(Invoker<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType);
46+
}
3647

37-
public object Get(object obj, string name, TemplateContext ctx)
38-
{
39-
return _invoker?.Invoke(obj);
48+
_invoker = (Invoker) Activator.CreateInstance(invokerType, [d]);
4049
}
4150

51+
public object Get(object obj, string name, TemplateContext ctx) => _invoker.Invoke(obj);
52+
4253
private static Delegate GetGetter(Type declaringType, string fieldName)
4354
{
44-
string[] names = [fieldName.ToLowerInvariant(), $"<{fieldName}>k__BackingField", "_" + fieldName.ToLowerInvariant()];
45-
46-
var field = names
47-
.Select(n => declaringType.GetField(n, BindingFlags.Instance | BindingFlags.NonPublic))
48-
.FirstOrDefault(x => x != null);
55+
string[] names = [fieldName.ToLowerInvariant(), $"<{fieldName}>k__BackingField", $"_{fieldName.ToLowerInvariant()}"];
4956

50-
if (field == null)
57+
foreach (var n in names)
5158
{
52-
return null;
53-
}
59+
var field = declaringType.GetField(n, BindingFlags.Instance | BindingFlags.NonPublic);
60+
if (field == null)
61+
{
62+
continue;
63+
}
5464

55-
var parameterTypes = new[] { typeof(object), declaringType };
65+
var parameterTypes = new[] { typeof(object), declaringType };
5666

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

59-
var emitter = method.GetILGenerator();
60-
emitter.Emit(OpCodes.Ldarg_1);
61-
emitter.Emit(OpCodes.Ldfld, field);
62-
emitter.Emit(OpCodes.Ret);
69+
var emitter = method.GetILGenerator();
70+
emitter.Emit(OpCodes.Ldarg_1);
71+
emitter.Emit(OpCodes.Ldfld, field);
72+
emitter.Emit(OpCodes.Ret);
6373

64-
return method.CreateDelegate(typeof(Func<,>).MakeGenericType(declaringType, field.FieldType));
74+
return method.CreateDelegate(typeof(Func<,>).MakeGenericType(declaringType, field.FieldType));
75+
}
76+
77+
return null;
6578
}
6679

67-
private interface IInvoker
80+
private abstract class Invoker
6881
{
69-
object Invoke(object target);
82+
public abstract object Invoke(object target);
7083
}
7184

72-
private sealed class Invoker<T, TResult> : IInvoker
85+
private sealed class Invoker<T, TResult>(Delegate d) : Invoker
7386
{
74-
private readonly Func<T, TResult> _d;
87+
private readonly Func<T, TResult> _d = (Func<T, TResult>) d;
7588

76-
public Invoker(Delegate d)
77-
{
78-
_d = (Func<T, TResult>)d;
79-
}
89+
public override object Invoke(object target) => _d((T) target);
90+
}
8091

81-
public object Invoke(object target)
82-
{
83-
return _d((T)target);
84-
}
92+
private sealed class BooleanInvoker<T>(Delegate d) : Invoker
93+
{
94+
private readonly Func<T, bool> _d = (Func<T, bool>) d;
95+
96+
public override object Invoke(object target) => _d((T) target) ? BooleanValue.True : BooleanValue.False;
97+
}
98+
99+
private sealed class Int32Invoker<T>(Delegate d) : Invoker
100+
{
101+
private readonly Func<T, int> _d = (Func<T, int>) d;
102+
103+
public override object Invoke(object target) => NumberValue.Create(_d((T) target));
85104
}
86105
}
87106
}

0 commit comments

Comments
 (0)