• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / FluentCommand / 26594173245

28 May 2026 06:28PM UTC coverage: 55.553% (+0.7%) from 54.902%
26594173245

push

github

pwelter34
Move JSON support, add docs and examples

1358 of 3215 branches covered (42.24%)

Branch coverage included in aggregate %.

103 of 234 new or added lines in 9 files covered. (44.02%)

371 existing lines in 26 files now uncovered.

4389 of 7130 relevant lines covered (61.56%)

312.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

18.38
/src/FluentCommand/Reflection/ExpressionFactory.cs
1
using System.Linq.Expressions;
2
using System.Reflection;
3

4
namespace FluentCommand.Reflection;
5

6
/// <summary>
7
/// Provides factory methods for creating delegates to dynamically invoke methods, constructors, properties, and fields using expression trees.
8
/// </summary>
9
internal static class ExpressionFactory
10
{
11
    /// <summary>
12
    /// Creates a delegate that invokes the specified method with the given instance and parameters.
13
    /// </summary>
14
    /// <param name="methodInfo">The method to invoke.</param>
15
    /// <returns>
16
    /// A <see cref="Func{T, TResult}"/> that takes an instance and an array of parameters, and returns the result of the method invocation.
17
    /// For void methods, returns <c>null</c>.
18
    /// </returns>
19
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="methodInfo"/> is <c>null</c>.</exception>
20
    public static Func<object?, object?[], object?> CreateMethod(MethodInfo methodInfo)
21
    {
22
        ArgumentNullException.ThrowIfNull(methodInfo);
×
23

24
        // parameters to execute
UNCOV
25
        var instanceParameter = Expression.Parameter(typeof(object), "instance");
×
26
        var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
×
27

28
        // build parameter list
UNCOV
29
        var paramInfos = methodInfo.GetParameters();
×
30
        var parameterExpressions = new Expression[paramInfos.Length];
×
31
        for (int i = 0; i < paramInfos.Length; i++)
×
32
        {
33
            // (Ti)parameters[i]
UNCOV
34
            var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
×
35

UNCOV
36
            Type parameterType = paramInfos[i].ParameterType;
×
37
            if (parameterType.IsByRef)
×
38
            {
UNCOV
39
                parameterType = parameterType.GetElementType()
×
40
                    ?? throw new InvalidOperationException($"By-ref parameter '{paramInfos[i].Name}' has no element type.");
×
41
            }
42

UNCOV
43
            parameterExpressions[i] = Expression.Convert(valueObj, parameterType);
×
44
        }
45

UNCOV
46
        var declaringType = methodInfo.DeclaringType
×
47
            ?? throw new InvalidOperationException($"Method '{methodInfo.Name}' has no declaring type.");
×
48

49
        // non-instance for static method, or ((TInstance)instance)
UNCOV
50
        var instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, declaringType);
×
51

52
        // static invoke or ((TInstance)instance).Method
UNCOV
53
        var methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);
×
54

55
        // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
UNCOV
56
        if (methodCall.Type == typeof(void))
×
57
        {
UNCOV
58
            var lambda = Expression.Lambda<Action<object?, object?[]>>(methodCall, instanceParameter, parametersParameter);
×
59
            var execute = lambda.Compile();
×
60

UNCOV
61
            return (instance, parameters) =>
×
62
            {
×
63
                execute(instance, parameters);
×
64
                return null;
×
65
            };
×
66
        }
67
        else
68
        {
UNCOV
69
            var castMethodCall = CastToObject(methodCall);
×
70
            var lambda = Expression.Lambda<Func<object?, object?[], object?>>(castMethodCall, instanceParameter, parametersParameter);
×
71

UNCOV
72
            return lambda.Compile();
×
73
        }
74
    }
75

76
    /// <summary>
77
    /// Creates a delegate that constructs an instance of the specified type using its parameterless constructor.
78
    /// </summary>
79
    /// <param name="type">The type to instantiate.</param>
80
    /// <returns>A <see cref="Func{TResult}"/> that creates an instance of the specified type, or <c>null</c> if no parameterless constructor exists.</returns>
81
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="type"/> is <c>null</c>.</exception>
82
    public static Func<object>? CreateConstructor(Type type)
83
    {
UNCOV
84
        ArgumentNullException.ThrowIfNull(type);
×
85

86
        var constructorInfo = type.GetConstructor(Type.EmptyTypes);
×
UNCOV
87
        if (constructorInfo == null)
×
88
            return null;
×
89

90
        var instanceCreate = Expression.New(constructorInfo);
×
UNCOV
91
        var instanceCreateCast = CastToObject(instanceCreate);
×
92

93
        var lambda = Expression.Lambda<Func<object>>(instanceCreateCast);
×
94

95
        return lambda.Compile();
×
96
    }
97

98
    /// <summary>
99
    /// Creates a delegate that gets the value of the specified property from an instance.
100
    /// </summary>
101
    /// <param name="propertyInfo">The property to get the value from.</param>
102
    /// <returns>
103
    /// A <see cref="Func{T, TResult}"/> that takes an instance and returns the property value as <c>object</c>,
104
    /// or <c>null</c> if the property is not readable.
105
    /// </returns>
106
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="propertyInfo"/> is <c>null</c>.</exception>
107
    public static Func<object, object?>? CreateGet(PropertyInfo propertyInfo)
108
    {
109
        ArgumentNullException.ThrowIfNull(propertyInfo);
4✔
110

111
        if (!propertyInfo.CanRead)
4!
112
            return null;
×
113

114
        var instance = Expression.Parameter(typeof(object), "instance");
4✔
115

116
        var declaringType = propertyInfo.DeclaringType
4!
117
            ?? throw new InvalidOperationException($"Property '{propertyInfo.Name}' has no declaring type.");
4✔
118

119
        var getMethod = propertyInfo.GetGetMethod(true)
4!
120
            ?? throw new InvalidOperationException($"Property '{propertyInfo.Name}' has no get method.");
4✔
121

122
        var instanceCast = CreateCast(instance, declaringType, getMethod.IsStatic);
4✔
123

124
        var call = Expression.Call(instanceCast, getMethod);
4✔
125
        var valueCast = CastToObject(call);
4✔
126

127
        var lambda = Expression.Lambda<Func<object, object>>(valueCast, instance);
4✔
128
        return lambda.Compile();
4✔
129
    }
130

131
    /// <summary>
132
    /// Creates a delegate that gets the value of the specified field from an instance.
133
    /// </summary>
134
    /// <param name="fieldInfo">The field to get the value from.</param>
135
    /// <returns>
136
    /// A <see cref="Func{T, TResult}"/> that takes an instance and returns the field value as <c>object</c>.
137
    /// </returns>
138
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="fieldInfo"/> is <c>null</c>.</exception>
139
    public static Func<object, object?> CreateGet(FieldInfo fieldInfo)
140
    {
UNCOV
141
        ArgumentNullException.ThrowIfNull(fieldInfo);
×
142

UNCOV
143
        var instance = Expression.Parameter(typeof(object), "instance");
×
144
        var declaringType = fieldInfo.DeclaringType
×
145
            ?? throw new InvalidOperationException($"Field '{fieldInfo.Name}' has no declaring type.");
×
146

147
        var instanceCast = CreateCast(instance, declaringType, fieldInfo.IsStatic);
×
148

149
        var fieldAccess = Expression.Field(instanceCast, fieldInfo);
×
UNCOV
150
        var valueCast = CastToObject(fieldAccess);
×
151

UNCOV
152
        var lambda = Expression.Lambda<Func<object, object>>(valueCast, instance);
×
153
        return lambda.Compile();
×
154
    }
155

156
    /// <summary>
157
    /// Creates a delegate that sets the value of the specified property on an instance.
158
    /// </summary>
159
    /// <param name="propertyInfo">The property to set the value on.</param>
160
    /// <returns>
161
    /// An <see cref="Action{T1, T2}"/> that takes an instance and a value to set,
162
    /// or <c>null</c> if the property is not writable.
163
    /// </returns>
164
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="propertyInfo"/> is <c>null</c>.</exception>
165
    public static Action<object, object?>? CreateSet(PropertyInfo propertyInfo)
166
    {
UNCOV
167
        ArgumentNullException.ThrowIfNull(propertyInfo);
×
168

UNCOV
169
        if (!propertyInfo.CanWrite)
×
UNCOV
170
            return null;
×
171

172
        var instance = Expression.Parameter(typeof(object), "instance");
×
UNCOV
173
        var value = Expression.Parameter(typeof(object), "value");
×
174

175
        var declaringType = propertyInfo.DeclaringType
×
UNCOV
176
            ?? throw new InvalidOperationException($"Property '{propertyInfo.Name}' has no declaring type.");
×
177

178
        var propertyType = propertyInfo.PropertyType;
×
UNCOV
179
        var setMethod = propertyInfo.GetSetMethod(true)
×
180
            ?? throw new InvalidOperationException($"Property '{propertyInfo.Name}' has no set method.");
×
181

UNCOV
182
        var instanceCast = CreateCast(instance, declaringType, setMethod.IsStatic);
×
183
        var valueCast = CreateCast(value, propertyType, false)
×
184
            ?? throw new InvalidOperationException($"Failed to create cast expression for property '{propertyInfo.Name}'.");
×
185

UNCOV
186
        var call = Expression.Call(instanceCast, setMethod, valueCast);
×
187
        var parameters = new[] { instance, value };
×
188

189
        var lambda = Expression.Lambda<Action<object, object?>>(call, parameters);
×
UNCOV
190
        return lambda.Compile();
×
191
    }
192

193
    /// <summary>
194
    /// Creates a delegate that sets the value of the specified field on an instance.
195
    /// </summary>
196
    /// <param name="fieldInfo">The field to set the value on.</param>
197
    /// <returns>
198
    /// An <see cref="Action{T1, T2}"/> that takes an instance and a value to set.
199
    /// </returns>
200
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="fieldInfo"/> is <c>null</c>.</exception>
201
    public static Action<object, object?> CreateSet(FieldInfo fieldInfo)
202
    {
UNCOV
203
        ArgumentNullException.ThrowIfNull(fieldInfo);
×
204

UNCOV
205
        var instance = Expression.Parameter(typeof(object), "instance");
×
UNCOV
206
        var value = Expression.Parameter(typeof(object), "value");
×
207

208
        var declaringType = fieldInfo.DeclaringType
×
209
            ?? throw new InvalidOperationException($"Field '{fieldInfo.Name}' has no declaring type.");
×
210

211
        var fieldType = fieldInfo.FieldType;
×
212

UNCOV
213
        var instanceCast = CreateCast(instance, declaringType, fieldInfo.IsStatic);
×
214

215
        var valueCast = CreateCast(value, fieldType, false)
×
UNCOV
216
            ?? throw new InvalidOperationException($"Failed to create cast expression for field '{fieldInfo.Name}'.");
×
217

UNCOV
218
        var member = Expression.Field(instanceCast, fieldInfo);
×
219
        var assign = Expression.Assign(member, valueCast);
×
220

221
        var parameters = new[] { instance, value };
×
222

UNCOV
223
        var lambda = Expression.Lambda<Action<object, object?>>(assign, parameters);
×
224
        return lambda.Compile();
×
225
    }
226

227

228
    /// <summary>
229
    /// Creates a cast expression for the given parameter and type, handling static and value types appropriately.
230
    /// </summary>
231
    /// <param name="instance">The parameter expression representing the instance or value.</param>
232
    /// <param name="declaringType">The type to cast to.</param>
233
    /// <param name="isStatic">Indicates whether the member is static.</param>
234
    /// <returns>
235
    /// A <see cref="UnaryExpression"/> representing the cast, or <c>null</c> if the member is static.
236
    /// </returns>
237
    private static UnaryExpression? CreateCast(ParameterExpression instance, Type declaringType, bool isStatic)
238
    {
239
        if (isStatic)
4!
UNCOV
240
            return null;
×
241

242
        // TypeAs (isinst) is faster than Convert (castclass) for reference types
243
        if (declaringType.IsValueType)
4!
UNCOV
244
            return Expression.Convert(instance, declaringType);
×
245
        else
246
            return Expression.TypeAs(instance, declaringType);
4✔
247
    }
248

249
    /// <summary>
250
    /// Creates the optimal expression for boxing/casting an expression result to <see cref="object"/>.
251
    /// Uses <c>Convert</c> (box) for value types and <c>TypeAs</c> (isinst) for reference types.
252
    /// </summary>
253
    private static UnaryExpression CastToObject(Expression expression)
254
    {
255
        // Value types: Convert emits a direct 'box' instruction.
256
        // Reference types: TypeAs emits 'isinst' which avoids the InvalidCastException
257
        // preparation overhead of 'castclass'; upcasting to object never fails.
258
        return expression.Type.IsValueType
4✔
259
            ? Expression.Convert(expression, typeof(object))
4✔
260
            : Expression.TypeAs(expression, typeof(object));
4✔
261
    }
262
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc