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

loresoft / FluentCommand / 23278216331

19 Mar 2026 03:19AM UTC coverage: 57.398% (+0.7%) from 56.658%
23278216331

push

github

pwelter34
Enable nullable and improve source generators

1403 of 3069 branches covered (45.72%)

Branch coverage included in aggregate %.

527 of 907 new or added lines in 58 files covered. (58.1%)

22 existing lines in 10 files now uncovered.

4288 of 6846 relevant lines covered (62.64%)

330.58 hits per line

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

16.88
/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
        if (methodInfo == null)
×
23
            throw new ArgumentNullException(nameof(methodInfo));
×
24

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

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

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

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

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

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

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

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

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

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

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

NEW
88
        var constructorInfo = type.GetConstructor(Type.EmptyTypes);
×
UNCOV
89
        if (constructorInfo == null)
×
NEW
90
            return null;
×
91

92
        var instanceCreate = Expression.New(constructorInfo);
×
NEW
93
        var instanceCreateCast = CastToObject(instanceCreate);
×
94

95
        var lambda = Expression.Lambda<Func<object>>(instanceCreateCast);
×
96

97
        return lambda.Compile();
×
98
    }
99

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

114
        if (!propertyInfo.CanRead)
4!
115
            return null;
×
116

117
        var instance = Expression.Parameter(typeof(object), "instance");
4✔
118

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

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

125
        var instanceCast = CreateCast(instance, declaringType, getMethod.IsStatic);
4✔
126

127
        var call = Expression.Call(instanceCast, getMethod);
4✔
128
        var valueCast = CastToObject(call);
4✔
129

130
        var lambda = Expression.Lambda<Func<object, object>>(valueCast, instance);
4✔
131
        return lambda.Compile();
4✔
132
    }
133

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

147
        var instance = Expression.Parameter(typeof(object), "instance");
×
NEW
148
        var declaringType = fieldInfo.DeclaringType
×
NEW
149
            ?? throw new InvalidOperationException($"Field '{fieldInfo.Name}' has no declaring type.");
×
150

151
        var instanceCast = CreateCast(instance, declaringType, fieldInfo.IsStatic);
×
152

153
        var fieldAccess = Expression.Field(instanceCast, fieldInfo);
×
NEW
154
        var valueCast = CastToObject(fieldAccess);
×
155

156
        var lambda = Expression.Lambda<Func<object, object>>(valueCast, instance);
×
157
        return lambda.Compile();
×
158
    }
159

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

174
        if (!propertyInfo.CanWrite)
×
175
            return null;
×
176

177
        var instance = Expression.Parameter(typeof(object), "instance");
×
178
        var value = Expression.Parameter(typeof(object), "value");
×
179

NEW
180
        var declaringType = propertyInfo.DeclaringType
×
NEW
181
            ?? throw new InvalidOperationException($"Property '{propertyInfo.Name}' has no declaring type.");
×
182

183
        var propertyType = propertyInfo.PropertyType;
×
NEW
184
        var setMethod = propertyInfo.GetSetMethod(true)
×
NEW
185
            ?? throw new InvalidOperationException($"Property '{propertyInfo.Name}' has no set method.");
×
186

187
        var instanceCast = CreateCast(instance, declaringType, setMethod.IsStatic);
×
NEW
188
        var valueCast = CreateCast(value, propertyType, false)
×
NEW
189
            ?? throw new InvalidOperationException($"Failed to create cast expression for property '{propertyInfo.Name}'.");
×
190

191
        var call = Expression.Call(instanceCast, setMethod, valueCast);
×
192
        var parameters = new[] { instance, value };
×
193

NEW
194
        var lambda = Expression.Lambda<Action<object, object?>>(call, parameters);
×
195
        return lambda.Compile();
×
196
    }
197

198
    /// <summary>
199
    /// Creates a delegate that sets the value of the specified field on an instance.
200
    /// </summary>
201
    /// <param name="fieldInfo">The field to set the value on.</param>
202
    /// <returns>
203
    /// An <see cref="Action{T1, T2}"/> that takes an instance and a value to set.
204
    /// </returns>
205
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="fieldInfo"/> is <c>null</c>.</exception>
206
    public static Action<object, object?> CreateSet(FieldInfo fieldInfo)
207
    {
208
        if (fieldInfo == null)
×
209
            throw new ArgumentNullException(nameof(fieldInfo));
×
210

211
        var instance = Expression.Parameter(typeof(object), "instance");
×
212
        var value = Expression.Parameter(typeof(object), "value");
×
213

NEW
214
        var declaringType = fieldInfo.DeclaringType
×
NEW
215
            ?? throw new InvalidOperationException($"Field '{fieldInfo.Name}' has no declaring type.");
×
216

UNCOV
217
        var fieldType = fieldInfo.FieldType;
×
218

219
        var instanceCast = CreateCast(instance, declaringType, fieldInfo.IsStatic);
×
220

NEW
221
        var valueCast = CreateCast(value, fieldType, false)
×
NEW
222
            ?? throw new InvalidOperationException($"Failed to create cast expression for field '{fieldInfo.Name}'.");
×
223

224
        var member = Expression.Field(instanceCast, fieldInfo);
×
225
        var assign = Expression.Assign(member, valueCast);
×
226

227
        var parameters = new[] { instance, value };
×
228

NEW
229
        var lambda = Expression.Lambda<Action<object, object?>>(assign, parameters);
×
230
        return lambda.Compile();
×
231
    }
232

233

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

248
        // TypeAs (isinst) is faster than Convert (castclass) for reference types
249
        if (declaringType.IsValueType)
4!
UNCOV
250
            return Expression.Convert(instance, declaringType);
×
251
        else
252
            return Expression.TypeAs(instance, declaringType);
4✔
253
    }
254

255
    /// <summary>
256
    /// Creates the optimal expression for boxing/casting an expression result to <see cref="object"/>.
257
    /// Uses <c>Convert</c> (box) for value types and <c>TypeAs</c> (isinst) for reference types.
258
    /// </summary>
259
    private static UnaryExpression CastToObject(Expression expression)
260
    {
261
        // Value types: Convert emits a direct 'box' instruction.
262
        // Reference types: TypeAs emits 'isinst' which avoids the InvalidCastException
263
        // preparation overhead of 'castclass'; upcasting to object never fails.
264
        return expression.Type.IsValueType
4✔
265
            ? Expression.Convert(expression, typeof(object))
4✔
266
            : Expression.TypeAs(expression, typeof(object));
4✔
267
    }
268
}
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