Cast Expression<Func<T>> to specific type

170 Views Asked by At

In a Blazor component, I have the following parameter. For is used to get some custom data annotations via reflection, e.g. to display an Asterisk * if a property is required and extract the label names based on data annotations

[Parameter]
public Expression<Func<T?>>? For { get; set; }

public Expression<Func<DateTime?>>? ForDateTime
{
    get
    {
        if (typeof(T) == typeof(DateTime))
        {
            //return <Func<DateTime?>>? here
        }
        else if (typeof(T) == typeof(DateTimeOffset))
        {
            //return <Func<DateTime?>>? here
        }
        else
        {
            return null;
        }
    }
    set
    {
        //For = set value of Expression<Func<T?>>? here
    }
}

How can I cast this to Expression<Func<DateTime?>>? ForDateTime { get; set; } and back if T is of Type DateTime or DateTimeOffset.

The reason for that is I am using MudBlazor and want to call

<MudDatePicker For="@(ForDateTime)" Variant="Variant.Outlined"/>

But there an Expression<Func<DateTime?>>? is needed.

For now I created a ExpressionVisitor like that:

public class TypeConverterVisitor<T, TU> : ExpressionVisitor
{
    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(T?) || node.Type == typeof(T))
        {
            return Expression.Parameter(typeof(TU?));
        }

        return base.VisitParameter(node);
    }
}

which I use like that:

var visitor = new TypeConverterVisitor<T, DateTime?>();
var expression = (Expression<Func<DateTime?>>)visitor.Visit(For);
return expression;

But this only works if my For is actually a (nullable) DateTime? already. I want it to be able to cast (non nullable) DateTime and DateTimeOffset as well. But I am not sure where I can handle this conversion. Especially as a breakpoint in the VisitParameter method is never hit.

1

There are 1 best solutions below

6
Guru Stron On

You can try using object indirection, but only if the T is constrained to struct (due to specifics of how T? is handled with introduction of nullable reference types - for example see Nullability and generics in .NET 6):

class Test<T> where T : struct
{
    public Expression<Func<T?>>? For { get; set; }

    public Expression<Func<DateTime?>>? ForDateTime
    {
        get
        {
            if (typeof(T) == typeof(DateTime))
            {
                return (Expression<Func<DateTime?>>)(object)For;
            }
            // ...
            else
            {
                return null;
            }
        }
        set
        {
            //For = set value of Expression<Func<T?>>? here
        }
    }
}

UPD:

If you can't constraint the class then you can rebuild expression tree. Something to get you started:

class Test<T>
{
    public Expression<Func<T?>>? For { get; set; }

    public Expression<Func<DateTime?>>? ForDateTime
    {
        get
        {
            if (typeof(T) == typeof(DateTime))
            {
                if (For is not null)
                {
                    var newBody = Expression.Convert(For.Body, typeof(DateTime?));
                    return Expression.Lambda<Func<DateTime?>>(newBody);
                }
                return null;
            }
            if (typeof(T) == typeof(DateTime?))
            {
                
                return  (Expression<Func<DateTime?>>)(object)For;
            }
            // ...
            else
            {
                return null;
            }
        }
        set
        {
            //For = set value of Expression<Func<T?>>? here
        }
    }
}
var forDateTime = new Test<DateTime>
    {
        For = () => DateTime.Now
    }
    .ForDateTime
    .Compile()();

var forDateTimeNull = new Test<DateTime?>
    {
        For = () => null
    }
    .ForDateTime
    .Compile()();