Wednesday, August 23, 2006

Friday, August 18, 2006

How to do late Dynamic method creation

In this post comment, I was asked about how to do creation of method delegates whose types can vary somewhat when using the Dynamic class. It's really a matter of using the unspecified form of a generic class and the Type.MakeGenericType method to synthesize up the desired fully specified type.  This code is going to look ugly, simply because playing with generics this way is more akin to writing a compiler than writing normal code.  Much of this code is boiler-plate and could easily be extracted into a set of helper methods... given some time, I'll do so in my current Dynamic class. Until then, here's an example of what things can look like:

// needed by both versions
BindingFlags creatorFlags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static;
Type staticProc = typeof(Dynamic<>.Static.Procedure.Explicit<,,>);
 
// this version "knows" the final argument type of the method, and could have been done explicitly
// I show this to make obvious the way you use MakeGenericType.
Type doubleProc = staticProc.MakeGenericType(typeof(PutMethods), typeof(IIdentifiable), typeof(string), typeof(double));
MethodInfo doubleProcCreateDelegate = doubleProc.GetMethod("CreateDelegate", creatorFlags, null, new Type[] { typeof(string) }, null); 
StaticProc<PutMethods, IIdentifiable, string, double> proc =
   doubleProcCreateDelegate.Invoke(null, new object[] { "Add" }) as StaticProc<PutMethods, IIdentifiable, string, double>
proc(null, "Age", 1.0);
 
// this is more like a normal use, where the final argument type is not known at delegate generation time...
Type lazyProc = staticProc.MakeGenericType(typeof(PutMethods), typeof(IIdentifiable), typeof(string), typeof(object));
MethodInfo lazyProcCreateDelegate = lazyProc.GetMethod("CreateDelegate", creatorFlags, null, new Type[] { typeof(string) }, null);
StaticProc<PutMethods, IIdentifiable, string, object> lazy =
   lazyProcCreateDelegate.Invoke(null, new object[] { "Add" }) as StaticProc<PutMethods, IIdentifiable, string, object>
lazy(null, "Balance", -10.0);

  
public interface IIdentifiable
{
   void SetProperty(string propertyName, object value);
} 
 
public class PutMethods
{
   public static void Add(IIdentifiable dr, string propertyName, double value)
   {
      if (dr != null)
         dr.SetProperty(propertyName, value);
   }
}

Note that you can easily build up the Type[] passed to Type.MakeGenericType based on things like an attribute class or configuration/XML markup.

Tuesday, August 15, 2006

The step-child gets some love...

Looks like Google is finally going to update Blog*Spot (a.k.a. Blogger in hosted mode).  Coming soon:

  • Dynamic content (not static pages) so no more republishing.
  • Access Control so you control who can read your blog.
  • Labels to let you tag the posts (why they couldn't call them tags, like the rest of the world, I don't know).
  • Easy layout to let people that don't get CSS or HTML markup do drag-and-drop stying of the blog.
  • More feed formats so you can choose the format you want to publish, and the default of Atom 0.3 will change to Atom 1.0.

I'm waiting anxiously to migrate this blog there and start tagging labelling things.

More information here.

Monday, August 14, 2006

Trying out Windows Live Writer...

I've used "Send to Blogger...", I've used "w:Blogger", so why not try Microsoft's latest stab at Googledom?  Looks neat so far. You can get more information over at Introducing Windows Live Writer

Tuesday, August 08, 2006

They're back!

Ahhh, the warm feelings of nostalgia wave over me... in less than a month, I get to see if the sweet memory is a suite reality. Check out the New Turbo C++/Turbo Delphi/Turbo C# homepage.

Putting it all together...

Jeremy D. Miller recapitulates the Best of the Shade Tree Developer. I nominate this as the most useful post of the year.

Wednesday, August 02, 2006

Yield to the power of the DataSource

The new declarative model for data sources in ASP.Net 2.0 is very seductive in its simplicity. I've all-too-often seen people doing date-picker and time-pickers by listing the times individually in ListItems. This leads to pages that have to be manually revisited if you change the range (start-time and end-time) or the increment (e.g. on-the-hour, on-the-half, etc.). It would be really cool to be able to drive these lists declaratively and bind them using the standard DataSourceID logic.

Using the new C# yield keyword it is trivial to return an IEnumerable<DateTime> that lists all the times (with a specified increment in minutes), days, weeks, months, etc. in a date range. I give you DateTimeDataSource:

using System;
using System.Collections.Generic;
using System.ComponentModel;
 
[DataObject(true)]
public class DateTimeDataSource
{
    private TimeSpan _DayStart = TimeSpan.FromHours(8); // default to 8am
    private TimeSpan _DayEnd = TimeSpan.FromHours(18); // default to 6pm
 
    [Browsable(true)]
    public TimeSpan DayStart
    {
        get { return _DayStart; }
        set { _DayStart = value; }
    }
 
    [Browsable(true)]
    public TimeSpan DayEnd
    {
        get { return _DayEnd; }
        set { _DayEnd = value; }
    }
 
    public static DateTime WeekStart(DateTime date)
    {
       return date.Date.AddDays(-(int)date.DayOfWeek);
    }
 
    public static DateTime WeekEnd(DateTime date)
    {
        return date.Date.AddDays(7).AddTicks(-1);
    }
 
    public static DateTime MonthStart(DateTime date)
    {
        return date.Date.AddDays(1 - date.Day);
    }
 
    public static DateTime MonthEnd(DateTime date)
    {
        return date.Date.AddMonths(1).AddTicks(-1);
    }
 
    public static DateTime YearStart(DateTime date)
    {
        return new DateTime(date.Year, 1, 1);
    }
 
    public static DateTime YearEnd(DateTime date)
    {
        return new DateTime(date.Year + 1, 1, 1).AddTicks(-1);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> ThisWeek()
    {
        DateTime start = WeekStart(DateTime.Today);
        DateTime end = WeekEnd(start);
        return Any(start, end, TimeSpan.FromDays(1));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> ThisMonth()
    {
        DateTime start = MonthStart(DateTime.Today);
        DateTime end = MonthEnd(start);
        return Any(start, end, TimeSpan.FromDays(1));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> Today(int minuteIncrement)
    {
        return Any(DateTime.MinValue, DateTime.MaxValue, minuteIncrement);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Today(TimeSpan increment)
    {
        return Any(DateTime.MinValue, DateTime.MaxValue, increment);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> Weeks(int numberOfWeeks)
    {
        DateTime start = WeekStart(DateTime.Today);
        DateTime end = start + TimeSpan.FromDays(7 * numberOfWeeks);
        return Weeks(start, end);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Weeks(DateTime start, int numberOfWeeks)
    {
        DateTime end = start + TimeSpan.FromDays(7 * numberOfWeeks);
        return Weeks(start, end);
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Weeks(DateTime start, DateTime end)
    {
        return Any(start, end, TimeSpan.FromDays(7));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public IEnumerable<DateTime> Any(DateTime start, DateTime end, int minuteIncrement)
    {
        return Any(start, end, TimeSpan.FromMinutes(minuteIncrement));
    }
 
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public IEnumerable<DateTime> Any(DateTime start, DateTime end, TimeSpan increment)
    {
        if (start == DateTime.MinValue || start.Ticks == 0)
        {
            start = DateTime.Today.Date + DayStart;
        }
 
        if (end == DateTime.MaxValue || end.Ticks == 0)
        {
            end = DateTime.Today.Date + DayEnd;
        }
 
        for (DateTime current = start; current <= end; current += increment)
        {
            yield return current;
        }
    }
}

Use this just like you would any other data source, for example, to build a RadioButtonList for the normal working hours (8am to 6pm, see notes below), you can do this:

<asp:ObjectDataSource ID="Hours" runat="server" CacheDuration="Infinite" EnableCaching="true"
        TypeName="DateTimeDataSource" SelectMethod="Today">
    <SelectParameters>
        <asp:Parameter Name="minuteIncrement" Type="Int32" Direction="Input" DefaultValue="30" />
    </SelectParameters>
</asp:ObjectDataSource>
<asp:RadioButtonList ID="RadioButtonTimeFrom" runat="server"
        DataSourceID="Hours" DataTextFormatString="{0:t}" />

Notes:

  • The properties for DayStart and DayEnd let you change the default start and end time for time-pickers.
  • The XXXXStart and XXXXEnd static methods are there to simplify the generation of date ranges.
  • This isn't necessarily completely correct with other calendar forms.

UPDATE: Blogger ate my || in the code of Any

Perma (as in PERMANENT) links that aren't annoy me...

Dear Microsoft Live.com team, Please stop assigning new link value to all the posts when you upgrade the software on Live.com. I really hate having to prune all the duplicates out of my RSS reader. Permalinks are supposed to be permanent, not "valid until further upgrades". Thank you and enjoy your day, Marc