You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无需显式指定TProperty类型创建Expression<Func<TModel, TProperty>>

Can we create Expression<Func<TModel, TProperty>> without explicitly specifying TProperty?

Great question! The short answer is: you can get close, but there are some tradeoffs since C#'s compile-time type inference can't resolve the property type from a string literal alone. Let's break down how to approach this.

The Core Challenge

When you call CreateExpression<SomeModel>("IsAlive"), the compiler has no way to statically know that "IsAlive" is a bool property on SomeModel—the string is just a runtime value. So we can't get full compile-time type safety for the TProperty parameter without a little extra work. But we have two solid approaches to make this work.


Approach 1: Runtime Reflection with Object Wrapping

We can create a non-generic overload of CreateExpression that uses reflection to find the property's type, then invokes the original generic method under the hood. The result will be wrapped as Expression<Func<TModel, object>>, which works for both value types and reference types (with boxing for value types).

Here's the code implementation:

public static class ExpressionBuilder
{
    // Public non-generic entry point
    public static Expression<Func<TModel, object>> CreateExpression<TModel>(string propertyName)
    {
        var modelType = typeof(TModel);
        // Find the property or throw if it doesn't exist
        var propertyInfo = modelType.GetProperty(propertyName) 
            ?? throw new ArgumentException($"Property '{propertyName}' not found on type '{modelType.FullName}'");

        // Get a reference to our generic CreateExpression method
        var genericMethod = typeof(ExpressionBuilder)
            .GetMethod(nameof(CreateExpressionGeneric), BindingFlags.NonPublic | BindingFlags.Static)
            ?.MakeGenericMethod(modelType, propertyInfo.PropertyType);

        if (genericMethod == null)
            throw new InvalidOperationException("Failed to locate generic expression builder method.");

        // Invoke the generic method to get the strongly-typed expression
        var typedExpression = genericMethod.Invoke(null, new object[] { propertyName }) as LambdaExpression;

        // Wrap value types in a Convert expression to object (handles boxing)
        if (propertyInfo.PropertyType.IsValueType)
        {
            var convertExpr = Expression.Convert(typedExpression.Body, typeof(object));
            return Expression.Lambda<Func<TModel, object>>(convertExpr, typedExpression.Parameters);
        }

        // For reference types, we can cast directly
        return typedExpression as Expression<Func<TModel, object>>;
    }

    // Internal generic method that builds the strongly-typed expression
    private static Expression<Func<TModel, TProperty>> CreateExpressionGeneric<TModel, TProperty>(string propertyName)
    {
        var parameter = Expression.Parameter(typeof(TModel), "model");
        var property = Expression.Property(parameter, propertyName);
        return Expression.Lambda<Func<TModel, TProperty>>(property, parameter);
    }
}

Usage Example:

// Returns Expression<Func<SomeModel, object>>, with the underlying body targeting a bool property
var isAliveExpr = ExpressionBuilder.CreateExpression<SomeModel>("IsAlive");
// Returns Expression<Func<SomeModel, object>> for a string property
var stringPropExpr = ExpressionBuilder.CreateExpression<SomeModel>("StringProperty");

If you need the exact strongly-typed expression later, you can cast it (with runtime checking):

var boolExpr = isAliveExpr as Expression<Func<SomeModel, bool>>;
if (boolExpr != null)
{
    // Use the strongly-typed expression here
}

Approach 2: Source Generators for Compile-Time Safety

If you want full compile-time type safety (so var lambda = CreateExpression<SomeModel>("IsAlive") is actually Expression<Func<SomeModel, bool>> at compile time), you can use a C# Source Generator. This tool runs during compilation, parses the property name string, resolves its type, and generates the strongly-typed code for you.

The basic steps for this approach are:

  • Create a source generator that detects calls to your CreateExpression method.
  • Use Roslyn's APIs to resolve the TModel type and the property name string literal.
  • Generate code that calls the generic CreateExpression<TModel, TProperty> with the correct TProperty type.
  • The compiler will use this generated code instead, giving you compile-time type checking.

This is more complex but gives you the best of both worlds: concise syntax and type safety.


Key Takeaways

  • Without source generators, you can't get compile-time type inference for TProperty from a string property name—since strings are runtime values.
  • The reflection approach is simple and works for most runtime scenarios, returning an Expression<Func<TModel, object>>.
  • Source generators let you achieve compile-time safety, but require more setup.

内容的提问来源于stack exchange,提问作者Rafael León

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:52:33