/// <summary>
/// Dynamically creates an Expression<Func<TProperty>> for a given property on an object instance.
/// This version specifically caters to converting non-nullable property types to their nullable
/// counterparts (e.g., bool to bool?) without introducing an Expression.Convert (UnaryExpression).
/// </summary>
/// <typeparam name="TProperty">The expected type of the value that the expression will return (e.g., bool?).</typeparam>
/// <param name="modelType">The actual Type of the model instance (e.g., typeof(MyTestModel)).</param>
/// <param name="propertyName">The name of the property to create the expression for (e.g., "BoolProp").</param>
/// <param name="instance">The actual instance of the model (e.g., new MyTestModel()).</param>
/// <returns>An Expression<Func<TProperty>> that represents accessing the property, with implicit conversion if necessary.</returns>
public static Expression<Func<TProperty>> CreateValueExpression<TProperty>(Type modelType, string propertyName, object instance)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance), "Model instance cannot be null.");
}
// Get the PropertyInfo for the specified property name from the model's type.
var property = modelType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
if (property == null)
{
throw new ArgumentException($"Property '{propertyName}' not found on type '{modelType.FullName}'.");
}
if (!property.CanRead)
{
throw new ArgumentException($"Property '{propertyName}' on type '{modelType.FullName}' is not readable.");
}
// Create a constant expression representing the actual model instance.
var instanceExpression = Expression.Constant(instance, modelType);
// Create an expression that represents accessing the property of the instance.
Expression propertyAccess = Expression.Property(instanceExpression, property);
// Determine the target type for the expression (TProperty)
Type targetType = typeof(TProperty);
// Determine the source type from the property
Type sourceType = property.PropertyType;
if (sourceType == targetType)
{
// No conversion needed, just directly create the lambda.
return Expression.Lambda<Func<TProperty>>(propertyAccess);
}
else
{
// If we reach here, it means the types don't match.
// Since the goal is to *avoid* UnaryExpression (Expression.Convert),
// we'll throw an exception here, guiding the user to ensure TProperty matches
// the property's actual type or its nullable counterpart if applicable.
throw new InvalidOperationException(
$"Cannot create Expression<Func<{targetType.Name}>> for property '{propertyName}' " +
$"of type '{sourceType.Name}'. To avoid UnaryExpression (Expression.Convert), " +
$"ensure that TProperty ({targetType.Name}) either exactly matches the property's actual type.");
}
}
}