在每次调用时重新评估常量 FieldExpression 的值

Tre*_*exx 5 .net c# expression

设置

我有一个我想定期调用的方法。该方法有两个 DateTime 参数。

public static object PrintDateTimes(DateTime fromDate, DateTime toDate)
{
    Console.WriteLine(
        string.Format("PrintDateTimes:\t{0} - {1} :: {2}", fromDate.ToLongTimeString(), toDate.ToLongTimeString(), Program.StartDateTimeString));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

该类Item提供方法Refresh和属性callback来存储应该被调用的方法。

public class Item
{
    public Expression<Func<object>> callback { get; set;  }
    
    public void Refresh()
    {
        this.Refresh(this.callback);
    }

    public void Refresh(Expression<Func<object>> callback)
    {
        MethodCallExpression methodBody = (MethodCallExpression)callback.Body;
        bool fromDateFound = false;
        bool toDateFound = false;

        foreach (var arg in methodBody.Arguments) {

            if (arg.Type == typeof(DateTime)) {

                if (fromDateFound && !toDateFound) {
                    
                    DateTime fromDate = Convert.ToDateTime(Expression.Lambda(arg).Compile().DynamicInvoke());

                    Console.WriteLine("newFromDate\t" + fromDate);
                    
                    toDateFound = true;

                } else if (!fromDateFound) {

                    DateTime toDate = Convert.ToDateTime(Expression.Lambda(arg).Compile().DynamicInvoke());

                    fromDateFound = true;

                    Console.WriteLine("newToDate\t" + toDate);
                }
            }
        }

        callback.Compile().Invoke();

        Console.WriteLine();
    }
Run Code Online (Sandbox Code Playgroud)

主要方法看起来像这样

public static string StartDateTimeString;

public static void Main()
{
    DateTime now = DateTime.Now;
    DateTime fromDate = DateTime.Now.AddHours(-0.5);
    DateTime toDate = DateTime.Now.AddHours(1);

    Program.StartDateTimeString = DateTime.Now.ToLongTimeString();

    Item item = new Item();

    // this is important
    AssignCallback(fromDate, toDate, item);

    timer = new Timer(timerCallback, item, Timeout.Infinite, Timeout.Infinite);

    timer.Change(0, Timeout.Infinite);
    while (true) ;
}

public static void AssignCallback(DateTime fromDate, DateTime toDate, Item item)
{
    item.callback = () => Program.PrintDateTimes(fromDate, toDate);
}
Run Code Online (Sandbox Code Playgroud)

最后是定时器回调

private static void timerCallback(object state)
{
    Item i = state as Item;

    i.Refresh();

    Console.WriteLine("Refresh at:\t" + DateTime.Now.ToLongTimeString());

    timer.Change(TimeSpan.FromSeconds(3), Timeout.InfiniteTimeSpan);
}
Run Code Online (Sandbox Code Playgroud)

问题

这是执行代码时的输出(并等待几秒钟)

Refresh at:     10:53:58
newToDate       10:23:58
newFromDate     11:53:58
PrintDateTimes: 10:23:58 - 11:53:58 :: 10:53:58

Refresh at:     10:54:01
newToDate       10:23:58
newFromDate     11:53:58
PrintDateTimes: 10:23:58 - 11:53:58 :: 10:53:58

Refresh at:     10:54:04
newToDate       10:23:58
newFromDate     11:53:58
PrintDateTimes: 10:23:58 - 11:53:58 :: 10:53:58

Refresh at:     10:54:07
newToDate       10:23:58
newFromDate     11:53:58
PrintDateTimes: 10:23:58 - 11:53:58 :: 10:53:58
Run Code Online (Sandbox Code Playgroud)

到目前为止一切顺利,但这不是所需的输出。调用回调时,将使用初始调用的 DateTime 值 - 但我希望在每次调用时再次评估它们,而不仅仅是传递它们的旧值。

所需的输出是这个(注意秒)

Refresh at:     10:53:58
newToDate       10:23:58
newFromDate     11:53:58
PrintDateTimes: 10:23:58 - 11:53:58 :: 10:53:58

Refresh at:     10:54:01
newToDate       10:23:01
newFromDate     11:53:01
PrintDateTimes: 10:23:01 - 11:53:01 :: 10:53:58

Refresh at:     10:54:04
newToDate       10:23:04
newFromDate     11:53:04
PrintDateTimes: 10:23:04 - 11:53:04 :: 10:53:58

Refresh at:     10:54:07
newToDate       10:23:07
newFromDate     11:53:07
PrintDateTimes: 10:23:07 - 11:53:07 :: 10:53:58
Run Code Online (Sandbox Code Playgroud)

我发现了什么

我注意到argin的类型foreach (var arg in methodBody.Arguments) {FieldExpression- afaik - 意味着它是恒定的。

如果我删除调用AssignCallback并直接分配回调,那么item.callback = () => PrintDateTimes(DateTime.Now.AddHours(-0.5), DateTime.Now.AddHours(1));它实际上可以工作。

可能因为 thenarg将是 type InstanceMethodCallExpressionN,所以没有常量。似乎将在每次调用时进行评估,而不是使用常量值传递。

有没有办法在每次调用时重新评估这些参数,以便正确更新值?

tim*_*mur 0

如果我正确地遵循您的示例,您的Refresh方法似乎不会更新值 - 它会选择您分配给它的任何表达式并一遍又一遍地重新评估它。

所以看来您缺少的是fromDate, toDate您的timerCallback. 我假设您有理由在那里使用表达式,但如果没有 - 一个简单的委托实际上可能会用更少的代码(和性能损失)来完成这项工作:

public class Item
{
    public Func<DateTime, DateTime, object> callbackFunc { get; set; } // to illustrate alternative point
    public Expression<Func<object>> callback { get; set; }

    public void Refresh()
    {
        Refresh(callback);
    }

    public void RefreshFunc(DateTime fromDate, DateTime toDate) // alternative execution path - see timerCallback()
    {
        callbackFunc(fromDate, toDate);
    }
    

    public void Refresh(Expression<Func<object>> callback)
    {
        //your code with no change where
    }
}

class Program
{
    public static object PrintDateTimes(DateTime fromDate, DateTime toDate)
    {
        Console.WriteLine($"PrintDateTimes:\t{fromDate.ToLongTimeString()} - {toDate.ToLongTimeString()} :: {Program.StartDateTimeString}");
        return 0;
    }

    public static string StartDateTimeString;
    private static Timer timer;

    public static void Main()
    {
        DateTime now = DateTime.Now;
        DateTime fromDate = now.AddHours(-0.5);
        DateTime toDate = now.AddHours(1);

        Program.StartDateTimeString = now.ToLongTimeString();

        Item item = new Item();

        // this is important
        AssignCallback(fromDate, toDate, item);

        timer = new Timer(timerCallback, item, Timeout.Infinite, Timeout.Infinite);

        timer.Change(0, Timeout.Infinite);
        while (true) ;
    }

    public static void AssignCallback(DateTime fromDate, DateTime toDate, Item item)
    {
        item.callback = () => Program.PrintDateTimes(fromDate, toDate);
    }
    private static void timerCallback(object state)
    {
        Item i = state as Item;
        // update the dates
        DateTime now = DateTime.Now;
        DateTime fromDate = now.AddHours(-0.5);
        DateTime toDate = now.AddHours(1);
        // call refresh delegate - you proably don't even need to wrap it in a method: i.callbackFunc(toDate, fromDate)
        i.RefreshFunc(fromDate, toDate);

        // or use your old method, but update the expression so it gets new values
        //AssignCallback(fromDate, toDate, i); // i'm not sure if you can do this in your actual code, but this basically reassigns the expression so it works
        //i.Refresh();

        Console.WriteLine("Refresh at:\t" + now.ToLongTimeString());

        timer.Change(TimeSpan.FromSeconds(3), Timeout.InfiniteTimeSpan);
    }
}
Run Code Online (Sandbox Code Playgroud)