Setup
I've got a method that I want to invoke periodically. The method has two DateTime parameter.
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;
}
The class Item provides the method Refresh and the property callback to store a method that's supposed to be invoked.
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();
}
The main method looks like this
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);
}
and finally the timer callback
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);
}
Problem
This is the output when executing the code (and waiting for a few seconds)
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
So far so good, however that's not the desired output. When invoking the callback, the DateTime values of the initial call will be used - but I want them to be evaluated again on every invoke, not just passed with their old value.
The desired output would be this (note the seconds)
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
What I've figured out
I've noticed that the type of arg in foreach (var arg in methodBody.Arguments) { is a FieldExpression which - afaik - means it's constant.
If I remove the call to AssignCallback and assign the callback directly, so item.callback = () => PrintDateTimes(DateTime.Now.AddHours(-0.5), DateTime.Now.AddHours(1)); it actually works.
Probably because then arg will be of the type InstanceMethodCallExpressionN, so no constant. Seems like the will be evaluated on each invoke rather than being passsed with a constant value.
Question
Is there a way to re-evaluate those parameter on each call, so that the values will be updated correctly?