Montag, 30. Januar 2012

Closures in C#

Closures kommen eigentlich auch den funktionalen Programmiersprachen. Einige Konzepte solcher Sprachen haben in C# einzug gehalten, und durch Lambda-Expressions zum Beispiel auch die Closures.

Hier ein kleines Beispiel:

public void DoSomething(int i)
{
  var x = 2 * i;
}

private Action GetAction(int initial)
{
  var i = 2 * initial;
  return () => DoSomething(i);
}

public void CallAction()
{
  Action a = GetAction(5);
  a();
}

Die Variable i in GetAction ist lokal und wird im Lambda-Ausdruck () => DoSomething(i) festgehalten. Beim Aufruf der Action in CallAction ist i eigentlich schon gar nicht mehr im Scope. Wegen des Closures kann der Delegat trotzdem darauf zugreifen.

Soweit so gut. Jetzt ein kleines Beispiel wo mich dieses Feature ein kleine Debugging-Session gekostet hat. Dazu ein vereinfachtes Beispiel:

private List<Action> actions = new List<Action>();

private void CreateActions()
{
  for (int i = 0; i < 10; i++)
  {
    var param = i * 2;
    AddAction(() => DoSomething(param));
  }
}

private void AddAction(Action a)
{
  if (!actions.Contains(a))
    actions.Add(a);
}

public void DoSomething(object i)
{
  var x = 2 * (int)i;
}

Jetzt werden die Actions zunächst in einer Liste gespeichert, aber nur falls die gleiche Action nicht schon enthalten ist.

Die Preisfrage lautet: wie viele Einträge enthält die Liste actions nach der Ausführung von CreateActions?

Die richtige Antwort ist: 10. Denn obwohl der Ausdruck () => DoSomething(param) immer gleich aussieht, ist es durch den Closure jedesmal eine andere Action, weshalb die Bedingung !actions.Contains(a) niemals greift.

Bei folgender Varianten ist die Menge der Varianten in der Liste gleich 1:

private void CreateActions2()
{
  for (int i = 0; i < 10; i++)
  {
    AddAction(() => DoSomething(1));
  }
}

Soweit klar, hier wird ja kein Element aus dem externen Scope eingeschlossen.
Aber auch diese Variante liefert einen Eintrag in der Liste actions:

private object context = 1;
private void CreateActions3()
{
  for (int i = 0; i < 10; i++)
  {
    context = 2 * i;
    AddAction(() => DoSomething(context));
  }
}

Hier ist die Variable context ein Referenztyp und das Closure schlíeßt nur die Addresse der Variablen mit ein.
Wenn man es mal so einfach hinschreibt, eigentlich logisch :-)

Donnerstag, 5. Januar 2012

A BranchCreatedEvent and TFS Extensibility

There are many extension and integration points in TFS. A very fine thing is the event system. We can hook into this either by defining a web service that gets called by the TFS - this works fine with WCF. Or we define a server side plugin for the event we are interested in, by implementing a class that implements the ISubscriber interface and deploying this into TFS.

Most documentation found on the web is written for TFS 2010 but as of now everything is running well with my TFS vNetxt installation.

The BranchCreatedEvent
There are a lot of useful events, with TFS vNext the list still got longer compared to version 2010. I wanted to do something, everytime a branch is created, so i was looking for a BranchCreatedEvent. But surprise: there is no such event, neither in TFS 2010 nor in TFS vNext. A suggested solution was to create a service that polls for new branches. I am not happy with this approach, but there seems to be no other way. So I started thinking about where to put my polling service.
One opportunity was to create a long running WCF service. I discarded that, because I was not sure wehter the service would restart once the App Pool was recycled. Another option was to write a custom Windows Service. Regarding this solution I was concerned that I would end up with a new services for each new requirement. So I thougth to implement a plugin based service to have on spot to add new features.

A plugin  based Task Scheduler for TFS  
So I wanted to have a task scheduler that could be extented thorugh plugins. And surprise again, there is already such a thing in TFS, called the TFS Job Agent - the thing that is also responsible for initiating the event processing. So all I had to was to find out how to implement a plugin for this agent.

Implementing a custom TFS Job Agent Job
Information about how to accomplish this was a little harder to find. This one put me on track, and other nice information can be found here.

Here is how the story goes:

Create a new class library solution and add the follwoing references:
  • Microsoft.TeamFoundation.Client [GAC]
  • Microsoft.TeamFoundation.Common [GAC]
  • Microsoft.TeamFoundation.Framework.Server [C:\Program Files\Microsoft Team Foundation Server Dev11\Application Tier\TFSJobAgent\]
Add a class implementing the ITeamFoundationJobExtension interface:

    public class MyFirstJob : ITeamFoundationJobExtension 
    {
        public TeamFoundationJobExecutionResult Run(TeamFoundationRequestContext requestContext, TeamFoundationJobDefinition jobDefinition, DateTime queueTime, out string resultMessage)
        {
            resultMessage = "Successfuly created my first job";
            return TeamFoundationJobExecutionResult.Succeeded;
        }
    }

The following code is needed to register the job and get it executed every 30 seconds:

var tfsConfigServerUri = new Uri(String.Format("http://localhost:8080/tfs"));
var tfsConfigServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsConfigServerUri);
var service = tfsConfigServer.GetService<ITeamFoundationJobService>();

var definition = new TeamFoundationJobDefinition(
                    new Guid("E5B15F37-1B19-4014-B354-B6CA3DA908E7"),
                    "My First Job",
                    "Lab.TFSJob.FirstTry.MyFirstJob",
                    null,
                    TeamFoundationJobEnabledState.Enabled);

var schedule = new TeamFoundationJobSchedule(new DateTime(2012, 1, 5, 9, 0, 0), 30);
definition.Schedule.Add(schedule);
                
service.UpdateJob(definition);

To queue the job initially you can add the following line:

var Result = service.QueueJobNow(definition, false);

The dll must be deployed to the %ProgramFiles%\Microsoft Team Foundation Server Dev11\Application Tier\TFSJobAgent\plugins\ folder. After this the job agent service must be restarted once, otherwise the assembly will not be loaded. You can debug your job by attaching to the TFSJobAgent.exe process on the TFS machine.

Now only some logic to check wether there are new branches between two polls and you are done!

Enjoy!