Donnerstag, 23. Juni 2016

FAKE und Sass

Gerne wird CSS nicht mehr direkt selbst geschrieben, sondern mit Präprozessoren wie z.B. SASS erzeugt. Schnell werden dann Projekte mit meist node.js basierenden Hilfsprogrammen ausgestattet, die lokal natürlich immer prima funktionieren, aber auf CI-Servern durchaus Probleme bereiten können. Genau das ist uns kürzlich passiert.

Kurzerhand haben wir beschlossen, die wesentlichen Schritte auf unserem CI Server durch entsprechende Funktionalitäten von FAKE zu ersetzen, was neben durchgängiger Versionskontrolle nur noch Abhängigkeiten von NuGet Paketen hatte.

Und eine Bibliothek für die Compilierung von SASS in das FAKE Script einzubauen war selbst für mich als F# Neuling nicht das große Problem.

Die relevanten Teile des Build Scripts sehen so aus – vermutlich lässt sich noch einiges daran verbessern aber die Abhängigkeit zu "Fremdkörpern" ist hiermit verschwunden:

#r @"packages/FAKE/tools/FakeLib.dll"
#r @"packages/libsassnet/lib/net40/LibSass.x86.dll"
#r @"packages/libsassnet/lib/net40/libsassnet.dll"

open Fake
open System.IO
open LibSassNet

let compileScss files =
    let compile file =
        let filename ext = Path.ChangeExtension(file, ext)

        let compiler = new LibSassNet.SassCompiler()

        tracefn "Compiling %s..." file

        let generateOutput outputStyle cssFile =
            let mapFile = cssFile + ".map"
            let result = compiler.CompileFile(file, outputStyle, mapFile)
            File.WriteAllText(cssFile, result.CSS)
            File.WriteAllText(mapFile, result.SourceMap)

        generateOutput LibSassNet.OutputStyle.Compact (filename ".css")
        generateOutput LibSassNet.OutputStyle.Compressed (filename ".min.css")

    files

        |> Seq.iter compile

// Targets (Auszug)
Target "Css" (fun _ ->
    !! "**/Content/*.scss"
        |> compileScss


)
...

Donnerstag, 24. Dezember 2015

Akka.NET Adventskalender – Tür 24

Zum Schluss

Zum Abschluss unseres kleinen Ausflugs in die Welt von Akka.NET wollen wir uns noch ein ebenfalls häufig notwendiges Mittel ansehen: den Umgang mit Zeit. Relativ oft müssen wir nach einer bestimmten Zeitspanne entweder bestimmte Aktionen auslösen oder kontrollieren, ob eine Aktion ausgelöst wurde, damit wir eventuell passende Maßnahmen einleiten können. Der vollständige Code dieses Beispiels ist wieder im üblichen github Repository.

Und es ist eigentlich klar, dass wir nicht wieder selbst zum Schraubendreher greifen müssen. Auch dafür gibt es eine vorgefertigte Lösung. Nehmen wir an, wir hätten einen Aktor, dessen ActorRef über die Variable controller erreichbar ist und wir wollen ihm regelmäßig alle 300 Millisekunden aber frühestens in 2 Sekunden die Nachricht Tick senden. Das ist so einfach wie:

system
    .Scheduler
    .ScheduleTellRepeatedly(
        TimeSpan.FromSeconds(2),
        TimeSpan.FromMilliseconds(300),
        controller,
        new Tick(),
        ActorRefs.NoSender);

das einzige was zu erklären ist, ist das letzte Argument. Obiges Programmfragment stammt aus einem Kommandozeilen Programm. Das ist natürlich kein Aktor, kann also nicht als Absender der Nachricht eingetragen werden. Würden wir solch eine Nachricht aus einem Aktor heraus versenden, nutzen wir selbstverständlich Self (also uns) als Absender. Das ActorSystem ist von einem Aktor heraus über Context.System erreichbar.

Und damit sind wir so weit, dass wir unser letztes Akka.NET Programm in dieser Artikelserie von github herunterladen und einmal vorsichtig anstarten können.

Du wirst schnell die Logik hinter dem Programm verstehen: Sämtliche Ausgaben auf den Bildschirm werden aus Gründen der Synchronisierung durch den Writer Aktor ausgeführt, der Controller empfängt alle 300 Millisekunden einen Tick und reagiert, indem er Lichter einer bestimmten Farbe jeweils ein- oder ausschaltet. Jedes einzelne Licht wird durch jeweils einen eigenen Light Aktor repräsentiert. Und wie bei unserem Sudoku Programm nutzen wir den Publish/Subscribe Mechanismus zur Kommunikation, anstelle die einzelnen Aktoren miteinander bekannt zu machen.

Frohes Fest!

Wenn Dir die Artikelserie gefallen hat, schreib etwas darüber. Hat Dir die Artikelserie oder einzelne Teile davon nicht gefallen, schreib mir bitte. Wir haben große Teile von Akka.NET vollkommen unberührt gelassen z.B. Remote, Cluster, Persistence, Tests. Wenn Interesse daran besteht, wären weitere Artikel zu diesen Themen oder ein Workshop auf einer geeigneten Konferenz sicher denkbar.

Ich wünsche euch frohe Weihnachten und ein gutes neues Jahr!

Mittwoch, 23. Dezember 2015

Akka.NET Adventskalender – Tür 23

Hab was vergessen

Leider hat unsere gestrige Variante es nicht geschafft, das schwierige Sudoku zu lösen. Natürlich sind wir selbst daran schuld, denn wir haben eine Sudoku Regel schlicht vergessen zu implementieren. "Jede Ziffer darf pro Zeile, Spalte oder Block nur einmal auftreten." Dass das einfache Sudoku geklappt hat, war purer Zufall (ehrlich gesagt musste ich ein wenig suchen, um ein geeignetes einfaches Sodoku zu finden...). Wir brauchen also noch ein paar weitere Dinge, damit wir auch schwierige Sudoku Aufgaben lösen können.

Als erstes müssen wir uns darum kümmern, dass jemand die Häufigkeit der potentiell platzierbaren Ziffern pro Zeile, Spalte und Block für uns mit verfolgt. Jedesmal wenn eine Zelle eine Ziffer gesetzt bekommt, oder eine Ziffer als mögliche Lösung für eine Zelle ausschließt, müssen wir die passende Statistik dazu anpassen. Stellen wir dabei fest, dass eine Ziffer irgendwo exakt einmal auftritt, dann können wir daraus kombinieren, dass diese Ziffer in der jeweiligen Zeile, Spalte oder Block gesetzt werden darf – sie ist ja die einzige ihrer Art.

Grundsätzlich benötigen wir also 3 Typen (Zeile, Spalte, Block) mal 9 (für jede Ziffer) Aktoren für unsere Statistik nach diesem Muster. Aufgrund der einfacheren Lesbarkeit verzichten wir hier auf die Erzeugung einer Basisklasse und wiederholen den doch recht einfachen Code anstelle ihn zu generalisieren.

using System;
using Akka.Actor;
using SudokuSolver.Messages;
using System.Collections.Generic;
using System.Linq;

namespace SudokuSolver.Actors
{
    public class SudokuCol : SudokuActor
    {
        private readonly int col;

        private List<int> statistics;

        public SudokuCol(int col)
        {
            this.col = col;

            statistics = Enumerable.Range(1, 9).Select(_ => 9).ToList();

            Receive<SetDigit>(SetStatistics, s => s.Col == col);
            Receive<StrikeDigit>(UpdateStatistics, s => s.Col == col);
        }

        private void SetStatistics(SetDigit setDigit)
        {
            var digit = setDigit.Digit;

            statistics[digit - 1] = 1;
        }

        private void UpdateStatistics(StrikeDigit strikeDigit)
        {
            var digit = strikeDigit.Digit;

            if (--statistics[digit-1] == 1)
                Publish(new FindColDigit(col, digit));
        }
    }
}

Und wir müssen unsere Aktoren für die einzelnen Zellen dahingend schlauer machen, dass sie dann wenn Ziffern als potentielle Lösungen ausscheiden, jeweils die passende StrikeDigit Nachricht aussenden sowie auf FindXxxDigit Nachrichten reagieren.

Unsere Sudoku Aktoren für die einzelnen Zellen verarbeiten also diese 3 weiteren Nachrichten:

Receive<FindRowDigit>(FindDigitHandler, f => f.Row == row);
Receive<FindColDigit>(FindDigitHandler, f => f.Col == col);
Receive<FindBlockDigit>(FindDigitHandler, f => f.Block == block);

und reagieren darauf wie folgt:

private void FindDigitHandler(FindDigit findDigit)
{
    var digit = findDigit.Digit;

    if (possibleDigits.Contains(digit))
        Publish(new SetDigit(row, col, digit));
}


Damit haben wir auch die gestern vergessene letzte Sudoku Regel mit implementiert und zählen nun 108 Aktoren, die gemeinsam und allein durch wirres Hin- und Herschreien ein Sudoku lösen. Irgendwo faszinierend, dass das funktionert.

Wiederum ist für den fertigen Code ein github Repository vorhanden.

Dienstag, 22. Dezember 2015

Akka.NET Adventskalender – Tür 22

Lass mal hören

Gestern haben wir die Idee zur Lösung unseres Sudoku Spiels entwickelt (Jeder schreit herum, welche Ziffer er eben erhalten hat, andere ziehen daraus ihre Schlüsse).

Dazu müssen wir unser Feld aus 9x9 Aktoren aufbauen. Dabei müssen wir in keinster Weise daran denken, die einzelnen Aktoren miteinander zu verbinden, denn wir setzen ja auf das Muster "Publish/Subscribe", so dass Mitteilungen einfach abgesetzt werden und interessierte sich für den Empfang von Nachrichten anmelden. Damit ist die Konstruktion unseres Spielfeldes extrem einfach:

for (int row = 0; row < 9; row++)
    for (int col = 0; col < 9; col++)
        system.ActorOf(
            Props.Create(printer, row, col), 
            String.Format("{0}-{1}", row, col));

Die nahfolgend gelistete SudokuCell Klasse zeigt die wesentlichen Teile der Implementierung der Sudoku Zelle. Die einzige Nachricht, die wir bislang definiert haben, war SetCell, die das Kommando ist, mit dem wir die Ziffer einer Zelle direkt setzen können. Alle anderen Aktoren registrieren sich für diese Nachricht und verarbeiten sie, wenn die eben gesetzte Zelle in der gleichen Zeile, der gleichen Spalte oder im gleichen 3x3 Block sitzt.

Dazu nutzen wir eine spezielle Überladung der Receive<> Methode, die es uns erlaubt, ein Prädikat mit anzugeben, welches nur dann einen wahren Rückgabewert liefert, wenn wir uns für die Verarbeitung der Nachricht interessieren. Klingt in Worten schlimmer als im Code, den Du gleich sehen wirst. Die ersten Nachrichten vom Typ SetCell werden beim eintragen der berets bekannten Angaben unseres Sudokus ausgelöst. Damit allerdings werden weitere Zellen beeinflusst und das Sudoku löst sich so mit jeder zusätzlichen Angabe immer weiter.

public class SudokuCell : SudokuActor
{
    private readonly int row;
    private readonly int col;
    private readonly int block;
    private HashSet<int> possibleDigits;
 
    public SudokuCell(IActorRef printer, int row, int col)
        : base(printer)
    {
        this.row = row;
        this.col = col;
        this.block = row / 3 * 3 + col / 3;
  
        possibleDigits = new HashSet<int>(Enumerable.Range(1,9));
  
        Receive<SetDigit>(RestrictPossibilities, IsRowOrColOrBlock);
        Receive<SetDigit>(SolveCell, IsMyCell);
    }
 
    private bool IsRowOrColOrBlock(SetDigit setDigit)
    {
        if (IsMyCell(setDigit))
            return false;

        return setDigit.Row == row
            || setDigit.Col == col
            || setDigit.Block == block;
    }
 
    private bool IsMyCell(SetDigit setDigit)
    {
        return setDigit.Row == row && setDigit.Col == col;
    }


    private void RestrictPossibilities(SetDigit setDigit)
    {
        var digit = setDigit.Digit;
  
        if (possibleDigits.Contains(digit))
        {
            possibleDigits.Remove(digit);
            if (possibleDigits.Count == 1)
                Publish(new SetDigit(row, col, possibleDigits.First()));
        }
    }
    
    private void SolveCell(SetDigit setDigit)
    {
        var digit = setDigit.Digit;
  
        possibleDigits.Clear();
        possibleDigits.Add(digit);
    }
 
    private bool IsSolved(int digit)
    {
        return possibleDigits.Contains(digit) && possibleDigits.Count == 1;
    }
}

Eigentlich war es das. Gut, es kommen noch ein paar Kleinigkeiten dazu, damit das ganze tatsächlich funktioniert. Aber bevor wir solche Dinge herunterleiern und Du mühsam alles zusammentragen musst, lade Dir lieber den Code von meinem github Repository.

Wenn Du experimentierfreudig bist, und in der Kommandozeilen-Anwendung versuchst, das schwierige Soduko auflösen zu lassen, wirst Du eine Überraschung erleben – es löst sich nicht. Unser Ansatz war also doch zu einfach.

Müssen wir also morgen nochmal ran.

Montag, 21. Dezember 2015

Akka.NET Adventskalender – Tag 21

Was der Franke unter "blägn" versteht

Während der vergangenen Sprints haben wir uns immer an einzelne bekannte Aktoren gewandt, wenn wir Nachrichten verschickt haben. Diese Woche werden wir uns eine weitere Benachrichtigungs-Strategie ansehen: Ausstrahlung (Broadcast). Das bedeutet, dass wir einen Absender einer Nachricht und keinen, einen oder beliebig viele Empfänger dieser Nachricht haben werden.

Dieses Muster wird auch als "Publish/Subscribe" bezeichnet. Selbstverständlich beherrscht Akka.NET auch dieses Muster. Diesmal werden wir nicht nochmal das Rad neu erfinden, indem wir dieses Muster nachprogrammieren. Stattdessen werden wir direkt die Möglichkeiten nutzen, die wir standardgemäß zur Verfügung haben.

Um eine Nachricht oder ein Ereignis (Ereignisse waren definiert als etwas vergangenes, daher nutzen wir in solchen Fällen gerne die Vergangenheit beim Verb) ausstrahlen möchten, sieht das so aus:

Context.System.EventStream.Publish(new SomethingHappened());

Wenn wir auf ein Ereignis oder eine Nachricht lauschen wollen, dann können wir eine dieser Methoden dazu einsetzen:

protected override void PreStart()
{
    // the most basic way of subscribing
    Context.System.EventStream.Subscribe(Self, typeof(SomethingHappened));
 
    // if you are `using Akka.Event;` you might also do:
    Context.System.EventStream.Subscribe<SomethingHappened>(Self);
}

Um zu demonstrieren, wie leistungsfähig das Ausstrahlen und Abonnieren von Nachrichen ist, werden wir ein kleines Sudoku Spiel zusammen programmieren. Die Idee dazu kam mir kürzlich nach einem Event-Storming Workshop, bei dem knapp 20 Personen im Raum scheinbar planlos umherschossen und am Ende ein durchdachtes Konzept an der Tafel stand. Faszinierend :-)

Und genau so können wir beim Sudoku vorgehen. Wir setzen in jedes einzelne Feld einen Aktor, der für genau dieses Feld zuständig ist. Jeder dieser Aktoren schnappt wertvolle Hinweise darüber auf, ob in seiner Zeile, seiner Spalte oder seinem Block etwas verändert wurde und streicht eventuell eben gesetzte Ziffern von seiner Liste der noch zur Verfügung stehenden Möglichkeiten. Bleibt nur noch eine Ziffer übrig, so ist diese Zelle gelöst. Das wiederum erfahren alle anderen und so löst sich das Rätsel schrittweise von selbst.

Da wir noch mehr Aktoren brauchen werden, packen wir die von allen gemeinsam genutzten Funktionalitäten in eine Basisklasse (morgen gibt es wieder ein github Repository). In dieser Basisklasse verhalten wir uns auch sehr tolerant, indem wir nicht verarbeitete Ereignisse einfach ignorieren. Normalerweise sollte man sich so etwas wirklich gut überlegen.

using Akka.Actor;
using Akka.Event;
using SudokuSolver.Messages;

namespace SudokuSolver.Actors
{
    /// 
    /// Base class for all Sudoko actors
    /// 
    public class SudokuActor : ReceiveActor
    {
        private readonly IActorRef printActor;

        public SudokuActor(IActorRef printActor)
        {
            this.printActor = printActor;
        }

        protected override void PreStart()
        {
            Context.System.EventStream.Subscribe<SetDigit>(Self);
        }

        protected void Publish(object message)
        {
            Context.System.EventStream.Publish(message);
        }

        protected override void Unhandled(object message)
        {
            // we do nothing, just ignore unhandled messages
        }
    }
}

und natürlich müssen wir die Nachricht definieren, die die Ziffer einer Zelle setzt. Darauf lauschen ja alle Zellen und ziehen ihre Schlußfolgerungen.

namespace SudokuSolver
{
    public class SetDigit
    {
        public int Row { get; set; }
        public int Col { get; set; }
        public int Block { get { return Row / 3 * 3 + Col / 3; } }

        public int Digit { get; set; }

        public SetDigit(int row, int col, int digit)
        {
            Row = row;
            Col = col;
            Digit = digit;
        }
    }
}

Wie wir dann tatsächlich das Sudoku lösen, verrate ich euch morgen.

Sonntag, 20. Dezember 2015

Akka.NET Adventskalender – Tag 20

Nur zusammen ergibt alles Sinn

An den vergangenen zwei Tagen haben wir uns theoretisch mit Map/Reduce befasst. Heute wollen wir uns die Programmierung dazu näher ansehen (Hinweis: am Ende steht ein Link auf ein github Repository – Du musst also nicht so viel tippen heute).

Der erste Aktor, der die Rohdaten verarbeitet, ist der Map Aktor. Wie beabsichtigt erzeugt er eine Liste von {Sprache, Anzahl} Tupeln, wobei die Anzahl für alle erzeugten Listeneinträge "1" ist. Das Ergebnis wird dem Absender geantwortet und kommt so beim Master wieder an.

using System;
using MapReduce.Messages;
using Akka.Actor;
using System.IO;
using System.Linq;

namespace MapReduce.Actors
{
    public class Mapper : ReceiveActor
    {
        public Mapper()
        {
            Receive<string>(Map);
        }

        private void Map(string input)
        {
            var mapResult = new MapResult();

            using (var reader = new StringReader(input)) 
            {
                string line;
                while ((line = reader.ReadLine()) != null) 
                {
                    if (!String.IsNullOrWhiteSpace(line))
                    {
                        var language = line.Split(new [] { '|' }).Last().Trim();

                        mapResult.Counts.Add(new LanguageCount(language));
                    }
                }
            }

            Sender.Tell(mapResult);
        }
    }
}

Die MapResult Nachricht, die der Map Aktor erzeugt, wird durch den Master an den Reduce Aktor weiter gereicht, der sie dann entsprechend gruppiert:

using System;
using Akka.Actor;
using MapReduce.Messages;

namespace MapReduce.Actors
{
    public class Reducer : ReceiveActor
    {
        public Reducer()
        {
            Receive<MapResult>(Reduce);
        }

        private void Reduce(MapResult mapResult)
        {
            var reduceResult = new ReduceResult();

            foreach (var count in mapResult.Counts)
            {
                if (reduceResult.Result.ContainsKey(count.Language))
                {
                    reduceResult.Result[count.Language] += count.Count;
                }
                else
                {
                    reduceResult.Result[count.Language] = count.Count;
                }
            }

            Sender.Tell(reduceResult);
        }
    }
}

Da sowohl der Map Aktor als auch der Reduce Aktor parallel arbeiten und beide mehrfach existieren, brauchen wir noch einen einzelnen Aktor, der alle Ergebnisse nochmals zusammenfasst. Der Code ist relativ ähnlich zum Reduce Aktor, allerdings wird das finale Ergebnis im Aggregator Aktor gespeichert und kann anschließend abgerufen werden.

using System;
using Akka.Actor;
using MapReduce.Messages;

namespace MapReduce.Actors
{
    public class Aggregator : ReceiveActor
    {
        private ReduceResult reduceResult;

        public Aggregator()
        {
            reduceResult = new ReduceResult();

            Receive<ReduceResult>(Aggregate);
            Receive<GetResult>(_ => Sender.Tell(reduceResult));
        }

        private void Aggregate(ReduceResult result)
        {
            foreach (var language in result.Result.Keys)
            {
                if (reduceResult.Result.ContainsKey(language))
                {
                    reduceResult.Result[language] += result.Result[language];
                }
                else
                {
                    reduceResult.Result[language] = result.Result[language];
                }
            }
        }
    }
}

Wie erwähnt, sind alle Beispiele in diesem git Repository auf github abgelegt.

Liefen wirklich die Aktoren parallel?

Falls es Dir auch schon aufgefallen ist: Glückwunsch! Kaum etwas lief parallel bislang. Wir hatten jeweils nur einen Aktor und die Daten flossen vom einen zum anderen. Einzig zu dem Zeitpunkt, als wir mehr als ein "Dokument" analysieren ließen, konnten der eine Map Aktor und der eine Reduce Aktor parallel laufen.

Aber Du erinnerst Dich sicher noch an unser Konstrukt von vor 3 Tagen? Wenn Du das nutzt, dann werden wir parallel ablaufende Aufgaben erreichen.

someActor = Context.ActorOf(
    Props.Create<SomeActorClass>()
         .WithRouter(new RoundRobinPool(NrWorkers))

Falls Du noch mehr Beispiele für den Einsatz von Map/Reduce suchst, mir hat das Beispiel "Freunde finden" sehr gefallen.

Morgen werden wir unseren letzten Sprint starten. Wir haben bisher einzelne und zahlreiche Aktoren untersucht. Was Nachrichten anging, haben wir stets einen Empfänger addressiert. Das werden wir nächste Woche ändern.

Samstag, 19. Dezember 2015

Akka.NET Adventskalender – Tür 19

Datenmengen schrittweise in Griff bekommen

Gestern haben wir uns mit den Grundlagen von Map/Reduce befasst und die Vorteile dieses Algorithmus bei großen Datenmengen kennengelernt. Heute werden wir uns an unserem konstruierten Beispiel ansehen, welche Schritte notwendig sind.

Als erstes benötigen wir eine zentrale Instanz. Nennen wir ihn Master. Er koordiniert alle anderen Beteiligten, verteilt und sammelt wieder ein.

using System;
using Akka.Actor;
using MapReduce.Actors;
using MapReduce.Messages;

namespace MapReduce.Actors
{
    public class Master : ReceiveActor
    {
        IActorRef mapper;
        IActorRef reducer;
        IActorRef aggregator;

        public Master()
        {
            mapper = Context.ActorOf(Props.Create<Mapper>());
            reducer = Context.ActorOf(Props.Create<Reducer>());
            aggregator = Context.ActorOf(Props.Create<Aggregator>());

            // 1. forward a string to mapper
            Receive<string>(mapper.Tell);

            // 2. forward map result to reducer
            Receive<MapResult>(reducer.Tell);

            // 3. forward reduce result to aggregator
            Receive<ReduceResult>(aggregator.Tell);

            // allow asking for aggregated result at any time
            Receive<GetResult>(aggregator.Forward);
        }
    }
}

Als nächstes definieren wir die diversen Nachrichten, die von den diversen Beteiligten gesendet und empfangen werden.

In Objekten der Klasse LanguageCount wollen wir Tupel der Form (Sprache, Anzahl) festhalten. Wir werden das öfter benötigen.

namespace MapReduce.Messages
{
    public class LanguageCount
    {
        public string Language { get; set; }
        public int Count { get; set; }

        public LanguageCount(string language, int count = 1)
        {
            Language = language;
            Count = count;
        }
    }
}

Der Map Schritt erzeugt Listen solcher Tupel:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MapReduce.Messages
{
    public class MapResult
    {
        public List<LanguageCount> Counts { get; set; }

        public MapResult()
        {
            Counts = new List<LanguageCount>();
        }

        public override string ToString()
        {
            return string.Format("[MapResult: {0}]", 
                String.Join(", ", 
                    Counts.Select(c => String.Format("{0}:{1}", 
                        c.Language, c.Count))
                )
            );
        }
    }
}

Wie schon erwähnt wird diese Nachricht dann an den Reduce Schritt übergeben, der dann eine nach Sprache gruppierte Liste erzeugt. Das ist mit einer Dictionary in .NET wunderbar abbildbar.

using System;
using System.Collections.Generic;
using System.Linq;

namespace MapReduce.Messages
{
    public class ReduceResult
    {
        public Dictionary<string,int> Result { get; set; }

        public ReduceResult()
        {
            Result = new Dictionary<string,int>();
        }

        public override string ToString()
        {
            return string.Format("[ReduceResult: {0}]", 
                String.Join(", ", 
                    Result.Keys.Select(l => String.Format("{0}:{1}", l, Result[l]))
                )
            );
        }

    }
}

Mit diesen Hilfsmitteln ausgestattet werden wir morgen die notwendige Logik programmieren.