Category: Microsoft .NET

  • Mitt meddelandesystem för Windows är uppdaterat till .NET 8

    Meddelandehantering för Windows löser det tekniska problemet man ser vid t.ex. utvecklandet av ett poker-spel för datorer: Olika ageranden är viktiga vid olika tillfällen. Jag har uppdaterat mitt meddelandesystem till .NET 8.

    Installation

    Install-Package SynchronousMessageSystem

    Grundläggande användning

    Meddelandemönstret visas här.

    Features

    Multiple message receivers
    Mutate the list receivers
    Custom actor matching
    Actor mutation
    Advanced customization: Specifying target method

  • JetBrains Rider är gratis för hemmabruk

    År 2024 finns det en IDE som verkligen sticker ut, och det är Microsoft Visual Studio. Om du endast jobbar med C# så finns det en produkt som faktiskt är ännu bättre, nämligen JetBrains Rider. Visual Studio har varit gratis för privat bruk länge, men från och med nu kan du även använda Rider helt gratis för privat bruk. Och det är en varm rekommendation.

  • Nu släpps .NET 9.0 – utan meddelandehantering

    Nu i skrivande stund finns release candidate 2 av .NET 9.0 att ladda hem, så jag förmodar att det inte handlar om en särskilt lång tid innan det finns en version att använda i produktion. Både programmeringsspråket C# och själva .NET börjar på att kännas ganska färdigt, så det mesta som är nytt i version 9.0 sker under skalet. Men även bland de nya features som introduceras, så är prestanda viktigt. Till exempel introducerar man en feature toggle som helt enkelt kan trimma bort kod som programmeraren togglar bort! Andra nya features handlar om infrastruktur (.NET Aspire). Det var tal om att .NET 9.0 skulle tillhandahålla meddelandehantering (Eventing Framework), men eftersom .NET saknat meddelandehantering i alla år, så finns det idag väl etablerade tredjepartsbibliotek för meddelandehantering. För att inte röra runt i den grytan i onödan, har Eventing Framework kasserats. Åtminstone för tillfället.

    Förändringar man kanske inte märker av i vardagen är att man förbättrat garbage collectorn till att välja strategi beroende på applikationens storlek. Om en applikation är mindre, kommer garbage collectorn att prioritera en resurssnål strategi, men om applikationen är större så kommer strategin vara mer behovsprövad, likt vad som redan är fallet idag. På detta vis kommer små applikationer inte ha de prestandastörningar som annars utmärker programmeringsspråk med garbage collection (alltså det som Microsoft kallar för “hanterade språk”, som t.ex. C# och Java, till skillnad från “ohanterade språk” som C och C++).

    Det är såklart bra att C# blir snabbare och snabbare, men särskilt spännande är det inte att växla från .NET 8.0 (med C# 12) till .NET 9.0 (med C# 13), och jag är nyfiken på vad som kommer att hända i .NET 10.0. Jag gissar att plattformen kommer att röra sig mindre och mindre. Personligen hoppas jag på att Blazor WASM blir mer stabilt och att det blir enklare att göra små program som är self-contained. Det är dessutom fortfarande ganska komplicerat att använda .NET på Linux och Mac, men det kan såklart bero på att jag är en undermålig användare på dessa plattformar.

  • Microsoft Copilot vet vad ett rullande index är

    Jag behövde presentera data som skrivs uppifrån och ned i minnet i en lista sorterat efter ålder, där den äldsta kommer först. Men jag tänkte låta Microsoft Copilot jobbet. Här är replikskiftet:

    Jag: Jag skulle vilja ha hjälp med en kod i C#. Tänk dig en array innehållande 10 element numrerade 0 till 9. Tänk dig även en pekare som pekar på ett av dessa element. Hänger du med?

    Copilot: Absolut, jag hänger med! I C# kan vi inte direkt använda pekare som i C eller C++, men vi kan använda index för att referera till ett specifikt element i en array. Här är ett exempel på hur du kan skapa en array med 10 element och sedan referera till ett specifikt element:

    int[] array = new int[10]; // Skapar en array med 10 element
    
    for(int i = 0; i < array.Length; i++)
        array[i] = i; // Fyller arrayen med värden från 0 till 9
    
    int index = 5; // "Pekare" till det sjätte elementet i arrayen (index 5)
    Console.WriteLine("Elementet på position {0} är {1}", index, array[index]); // Skriver ut värdet på det sjätte elementet

    I det här exemplet skapar vi en array och fyller den med värden från 0 till 9. Sedan definierar vi en variabel index som “pekare” till det sjätte elementet i arrayen (index 5). Slutligen skriver vi ut värdet på det sjätte elementet. Hoppas det hjälper!

    Jag: Utmärkt. Men tänk dig nu att jag vill läsa ut ett element, relativt till indexet, så jag vill introducera en variabel till. Om någon vill läsa ut den första raden, så ska han tillhandahålla siffran 0, men den första raden är den raden som indexet pekar på. Om någon vill läsa ut den andra raden, ska han tillhandahålla siffran 1, men den andra raden är raden efter indexet, och så vidare.

    Copilot: Jag förstår, du vill ha en slags “rullande” index, där indexet pekar på “första” raden, och sedan räknas alla andra rader relativt till det indexet. Här är ett exempel på hur du kan göra det i C#:

    int[] array = new int[10]; // Skapar en array med 10 element
    
    for(int i = 0; i < array.Length; i++)
        array[i] = i; // Fyller arrayen med värden från 0 till 9
    
    int index = 5; // "Pekare" till det sjätte elementet i arrayen (index 5)
    int relativeRow = 1; // Den relativa raden vi vill läsa ut
    int actualIndex = (index + relativeRow) % array.Length; // Beräknar det faktiska indexet med hänsyn till arrayens längd
    Console.WriteLine("Elementet på relativa rad {0} är {1}", relativeRow, array[actualIndex]); // Skriver ut värdet på den relativa raden

    I det här exemplet använder vi modulusoperatorn (%) för att säkerställa att det faktiska indexet inte överstiger arrayens längd. Om det gör det, börjar det om från början av arrayen. Detta skapar en “rullande” effekt, där indexet alltid pekar på “första” raden, och alla andra rader räknas relativt till det indexet. Hoppas det hjälper!

    Notera att Copilot först antog att en pekare var just en minnespekare, och påstår felaktigt att sådana inte finns i C#. Men förstår därefter att jag menade elementpekare, och anpassar sitt språkbruk till detta, samt skriver en fungerande algoritm! Koden klippte jag ut och klistrade in i HUD-loggen i Altair 8800-emulatorn jag arbetar med.

    public class Log
    {
        public int StartPointer { get; set; }
        public string[] LogRows { get; }
    
        public Log()
        {
            LogRows = new string[10];
            StartPointer = 0;
        }
    
        public void Clear()
        {
            for (var i = 0; i < LogRows.Length; i++)
                LogRows[i] = "";
    
            StartPointer = 0;
        }
    
        public void Write(string text)
        {
            LogRows[StartPointer] = text;
            StartPointer++;
    
            if (StartPointer >= LogRows.Length)
                StartPointer = 0;
        }
    
        public string GetStringNumber(int number)
        {
            var pointer = (StartPointer + number) % LogRows.Length;
            return LogRows[pointer];
        }
    }

    Avslutningsvis har jag tagit för vana att även artigt tacka intelligenta entiteter, även om deras intelligens är en produkt av skapelse istället för evolution.

  • Varför method groups i C#? Jag är osäker!

    Här är jag lite osäker på vad man försöker göra med C#. En metod är en funktion som tillhör ett objekt. Konceptet känns igen från både C++, Java och C#. En s.k. method group (från nu: metodgrupp) är en C#-term för något som refererar till en eller flera metoder som delar namn. Metodgrupper liknar ungefär delegater, och jag misstänker att detta mest är ett syntaxsocker som inte gör mer för programmeraren än att minska antalet tecken som behöver förvaltas för en programfeature. Detta exempel skriver ordet “Fantastico” på skärmen, och det görs genom att metodgruppen DoSomething tilldelas till delegaten g, för att sedan anropas.

    var x = new MinTyp();
    var g = x.DoSomething;
    g();
    
    public class MinTyp
    {
        public void DoSomething(string value = "Fantastico") =>
            Console.WriteLine(value);
    }

    Till skillnad från en metod, som identifieras av sin signatur (namn och parametrar) så identifieras metodgruppen endast av sitt namn. Klassen MinTyp kan ha flera metoder som heter DoSomething (överlagringar). Exemplet ovan skulle inte kompilera om så vore fallet, eftersom kompilatorn inte skulle veta vilken DoSomething som åsyftas. Detta fungerar inte:

    var x = new MinTyp();
    var g = x.DoSomething;
    g();
    
    public class MinTyp
    {
        public void DoSomething(string value = "Fantastico") =>
            Console.WriteLine(value);
    
        public void DoSomething() =>
            Console.WriteLine("Hello!");
    }

    Men om jag specificerar vilken datatyp variabeln g har, så vet kompilatorn vad som åsyftas. Om jag t.ex. säger att g är en Action<string> och tillgodoser det när jag anropar g, så accepteras koden. Detta ger svaret “God dag!”:

    var x = new MinTyp();
    Action<string> g = x.DoSomething;
    g("God dag!");
    
    public class MinTyp
    {
        public void DoSomething(string value = "Fantastico") =>
            Console.WriteLine(value);
    
        public void DoSomething() =>
            Console.WriteLine("Hello!");
    }

    Det går även bra att fylla delegaten med fler metodgrupper som passar i den datatyp som delegaten fastslagits till. Här kommer ett anrop på g leda till ett anrop på DoSomething (överlagringen utan argument) och WriteAJoke:

    var x = new MinTyp();
    Action g = x.DoSomething;
    g += WriteAJoke;
    g();
    
    void WriteAJoke() =>
        Console.WriteLine("- What’s brown and sticky? - A stick.");
    
    public class MinTyp
    {
        public void DoSomething() =>
            Console.WriteLine("Hello!");
    }
    
    public class EnAnnanTyp
    {
        public static void DoSomething() =>
            Console.WriteLine("En statisk funktion");
    }

    Och det är egentligen där användbarheten når vägs ände. Det är snyggt, men det tillför just inget. När metodgrupper används i [lista].ForEach så sparar man förvisso en del tecken, men det kommer fortfarande att kompileras till en instansering av delegaten och en funktionspekare till den funktion som kompilatorn har bundit anropet till. Om jag har missat något, så skulle jag uppskatta en kommentar!

  • En ny terminalemulator för Windows Forms

    Jag har utvecklat en GUI control för Windows Forms (.NET 8.0) som emulerar en textterminal från 1970-talet. Den stöder in- och utmatning av text samt enkel pixelbaserad grafik. Den har events som meddelar vad användaren gör, och funktioner för att ställa frågor till användaren. Denna kan användas som editor för radbaserade programmeringsspråk och som gränssnitt för textbaserade applikationer (som t.ex. textäventyr).

    Här finns källkoden och några exempel, och här finns information om hur man installerar den och kommer igång.

  • Standardvärden som skickas till en metod i C#

    I C# kan standardvärden (default value) på en parameter i en metod sättas både i själva metoddeklarationen i klassen som håller metoden och i eventuella interfaces som klassen ärver ifrån. Dessa standardvärden behöver inte stämma överens med varandra, så vilket är det som gäller? Det är inte instansens typ utan variabelns typ som avgör parameterns standardvärde. Det innebär att man kan typomvandla för att styra vilka parametrar som skickas till en metod. Här är det de upprepade typomvandlingarna som gör att resultatet blir Good Day To You Sir:

    var a = new A();
    (a as B).X();
    (a as C).X();
    (a as D).X();
    (a as E).X();
    (a as F).X();
    
    class A : B, C, D, E, F
    {
        public void X(string y) => Console.WriteLine(y);
    }
    
    interface B { void X(string y = "Good"); }
    interface C { void X(string y = "Day"); }
    interface D { void X(string y = "To"); }
    interface E { void X(string y = "You"); }
    interface F { void X(string y = "Sir"); }

    Vill man vara riktigt äventyrlig så kan man skriva en iteration som tittar på den exakta arvslistan som är placerad på klassen A. Här är det den kommaseparerade listan som avgör vilket standardvärde som används.

    var a = new A();
    
    foreach (var p in typeof(A).GetInterfaces())
    {
        var m = p.GetMethods().Where(p => p.Name == "X").First();
        m.Invoke(a, new[] { m.GetParameters().First().DefaultValue });
    }
    
    class A : B, C, D, E, F
    {
        public void X(string y) => Console.WriteLine(y);
    }
    
    interface B { void X(string y = "Good"); }
    interface C { void X(string y = "Day"); }
    interface D { void X(string y = "To"); }
    interface E { void X(string y = "You"); }
    interface F { void X(string y = "Sir"); }

    Resultatet är återigen Good Day To You Sir. Egentligen inte helt ologiskt, om du någon gång funderar på vad som styr vilket standardvärde som skickas till parametern i din funktion, så titta på variabelns typ.

  • Det finns enorma optimeringsmöjligheter när man använder GDI från .NET

    När man ska jobba med bitmapsgrafik i .NET är prestanda en ständigt återkommande utmaning. I .NET har vi dels tillgång till GDI (som låter oss rita pixlar individuellt) och något som kallas för GDI+, som erbjuder möjligheten att rita geometriska figurer på skärmen. GDI är numera en del av GDI+, så båda biblioteken exponeras i namnrymden System.Drawing. Den viktigaste klassen för den som jobbar med GDI heter Bitmap och den viktigaste klassen för den som jobbar med GDI+ heter Graphics.

    Som parentes vill jag nämna att .NET inte är ett ramverk utan två. Dels har vi .NET Framework och dels har vi det ramverk som kort och gott heter .NET (tidigare .NET Core). Jag väljer i princip alltid .NET (tidigare .NET Core) eftersom det är ett mer moget ramverk, det finns tillgängligt på flest operativsystem (Mac, Linux, Android, m.fl.) och för att du får arbeta i senare versioner av C# i .NET. Det enda motivet att välja .NET Framework är att appar byggda för .NET Framework 4.8 fungerar out-of-the-box på Windows 10 eller senare och uppdateras via Windows Update. Eftersom GDI är en Windows-feature är jag inte intresserad av plattformsoberoendet och väljer därför .NET Framework 4.8 och C# 7.3 för dessa exempel.

    Använder man Visual Studio med tillägget för skrivbordsapplikationer, är det bara att välja en projektmall för Windows Forms och .NET Framework, men kör man Visual Studio Code eller någon annan editor, måste man specificera i projektfilen att man vill ha TargetFrameworkVersion satt till v4.8, att OutputType ska vara WinExe och att namnrymderna System.Windows.Forms och System.Drawing ska vara refererade.

    Låt oss titta på detta program. Det ritar en rektangel (500×500 pixlar) på skärmen varje gång man klickar med musen. Rektangeln ritas röd- och gulrandig på en slumpvis vald plats. Hela operationen klockas.

    using System;
    using System.Diagnostics;
    using System.Drawing;
    using System.Windows.Forms;
    
    namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {
            private static Random _rnd = new Random();
            private Bitmap Rectangle { get; set; }
            private int PositionX { get; set; }
            private int PositionY { get; set; }
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_MouseClick(object sender, MouseEventArgs e)
            {
                PositionX = _rnd.Next(300);
                PositionY = _rnd.Next(300);
    
                // Städa upp minnet.
                Rectangle?.Dispose();
    
                Rectangle = new Bitmap(500, 500);
    
                // Rita en randig rektangel.
                var c1 = Color.FromArgb(255, 0, 0);
                var c2 = Color.FromArgb(255, 255, 0);
    
                var stopwatch = new Stopwatch();
                stopwatch.Start();
    
                for (var y = 0; y < 500; y++)
                {
                    var currentColor = y%2 == 0 ? c1 : c2;
    
                    for (var x = 0; x < 500; x++)
                    {
                        Rectangle.SetPixel(x, y, currentColor);
                    }
                }
    
                // Skriv ut hur lång tid operationen tog.
                stopwatch.Stop();
                Text = stopwatch.ElapsedMilliseconds.ToString();
    
                // Uppdatera skärmen (Form1_Paint kommer att anropas).
                Invalidate();
            }
    
            private void Form1_Paint(object sender, PaintEventArgs e)
            {
                if (Rectangle == null)
                    return;
    
                // Här utförs uppdateringen av skärmen.
                e.Graphics.Clear(Color.White);
                e.Graphics.DrawImage(Rectangle, PositionX, PositionY);
            }
    
            private void Form1_FormClosed(object sender, FormClosedEventArgs e)
            {
                // Städa upp minnet.
                Rectangle?.Dispose();
            }
        }
    }

    Hela operationen tar ungefär 140 millisekunder oavsett om programmet är debug- eller release-kompilerat.

    Funktionen SetPixel är central för att detta ska fungera. Den har Microsoft skrivit åt oss i C#, och använder i sin tur GDI i Windows för att få pixeln satt på skärmen. Om vi läser källkoden så ser vi att den gör en del tester som säkerställer att funktionen används korrekt, för att sedan anropa GdipBitmapSetPixel i biblioteket gdiplus.dll som är skrivet i C++.

    Vi skulle kunna optimera bort testerna, men jag förstår att Microsoft fill ha med dem, eftersom de tänker att det måste vara bättre med ett program som är lite slöare än ett program som kraschar om någon gör ett fel, som t.ex. att försöka rita en pixel på en bitmapsbild vars position ligger utanför bildens storlek.

    En annan mindre viktig detalj är att för varje pixel som ritas, tar SetPixel en färg som är en struktur av en röd, grön och blå byte, som ska konverteras till en int. Konverteringen är gjord i förväg av färgstrukturen, men det blir ett extra funktionsanrop och en extra typomvandling.

    Dessa optimeringar får man såklart gärna titta på, om man vill få ut så hög prestanda man bara kan i sin renderingsrutin. Men den viktigaste faktorn handlar om låsning av minnet. När man ritar pixlar ska man erhålla ett lås för att skydda minnet, som man sedan släpper när man är färdig. Som SetPixel är implementerad, skapas och släpps ett lås för varje pixel som sätts, vilket kostar. Att göra en egen implementation som håller låset tills alla pixlar är ritade, är inte särskilt svårt, men det ställer lite högre krav på den som använder implementationen. Och det kanske var därför som Microsoft valde den väg man valde – säkerhet framför prestanda. Trots allt är tanken med C# att det ska vara mer lättillgängligt än t.ex. C++.

    Ett exempel på denna lättillgänglighet är att C#-kompilatorn helt enkelt inte tillåter användandet av pointers såvida man inte först deklarerar att man tänker göra så, vilket man gör med nyckelordet unsafe. Det är till och med så att nyckelordet unsafe inte får användas om vi inte sätter AllowUnsafeBlocks till true i projektfilen!

    För att undvika att minnet blir låst för varje pixel som ritas, måste vi undvika den inbyggda funktionen SetPixel. Och om vi manipulerar minnet utan att använda den inbyggda funktionen SetPixel, måste vi manuellt låsa minnet när vi börjar arbeta och låsa upp det när vi är klara.

    Funktionen LockBits behöver kunna räkna ut hur mycket minne som ska låsas och behöver därför veta bildens storlek (500×500 pixlar) samt vilket pixelformat som används. Jag utnyttjar bara 24 bitar (röd x grön x blå) men arbetar med 32 bitar (alfa x röd x grön x blå), så därför anger jag pixelformatet Format32bppArgb. Svaret från funktionen LockBits används för att berätta för funktionen UnlockBits vad som ska låsas upp, så det vill man ta hand om.

    Sist men inte minst måste färgen på pixlarna sättas. Tidigare var det bara ytterligare en parameter till SetPixel, men nu måste även det göras manuellt. Eftersom vi arbetar i 24 bitar, kan vi använda en fyra bytes stor pointer, Scan0.

    using System;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {
            private static Random _rnd = new Random();
            private Bitmap Rectangle { get; set; }
            private int PositionX { get; set; }
            private int PositionY { get; set; }
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_MouseClick(object sender, MouseEventArgs e)
            {
                PositionX = _rnd.Next(300);
                PositionY = _rnd.Next(300);
    
                // Städa upp minnet.
                Rectangle?.Dispose();
    
                var bits = new int[500 * 500];
                var bitsHandle = GCHandle.Alloc(bits, GCHandleType.Pinned);
                Rectangle = new Bitmap(500, 500, 500 * 4,
                    PixelFormat.Format32bppArgb,
                    bitsHandle.AddrOfPinnedObject());
    
                // Rita en randig rektangel.
                var c1 = Color.FromArgb(255, 0, 0).ToArgb();
                var c2 = Color.FromArgb(255, 255, 0).ToArgb();
    
                var stopwatch = new Stopwatch();
                stopwatch.Start();
    
                var data = Rectangle.LockBits(
                    new Rectangle(0, 0, 500, 500),I
                    mageLockMode.ReadWrite,
                    PixelFormat.Format32bppArgb);
    
                unsafe
                {
                    var bytes = (byte*)data.Scan0;
    
                    for (var y = 0; y < 500; y++)
                    {
                        for (var x = 0; x < 500; x++)
                        {
                            var index = x + y * 500;
                            bits[index] = y % 2 == 0 ? c1 : c2;
                        }
    
                        bytes++;
                    }
                }
    
                Rectangle.UnlockBits(data);
                bitsHandle.Free();
    
                // Skriv ut hur lång tid operationen tog.
                stopwatch.Stop();
                Text = stopwatch.ElapsedMilliseconds.ToString();
    
                // Uppdatera skärmen (Form1_Paint kommer att anropas).
                Invalidate();
            }
    
            private void Form1_Paint(object sender, PaintEventArgs e)
            {
                if (Rectangle == null)
                    return;
    
                // Här utförs uppdateringen av skärmen.
                e.Graphics.Clear(Color.White);
                e.Graphics.DrawImage(Rectangle, PositionX, PositionY);
            }
    
            private void Form1_FormClosed(object sender, FormClosedEventArgs e)
            {
                // Städa upp minnet.
                Rectangle?.Dispose();
            }
        }
    }

    Och skillnaden går inte av för hackor! Den lite mer komplicerade koden till trots, nu går samma operation på en (!) millisekund! Man kan alltså lugnt konstatera att om prestanda är viktigt, så bör man kringgå funktionen SetPixel.

  • Change configuration values in runtime in .NET Framework

    I stumpled upon the problem of having to change the configuration values in an old .NET Framework 4.0 system while it is running, and it has been possible to do since .NET Framework 2.0.

    The compiled App.config file will be named like the assembly it belongs to with the ending .config added to it (e.g. MyProgram.exe.config). Even if your settings are read from and/or used in a DLL, the settings should be written in the main program’s configuration file, not the DLL’s own config file.

    You might know that a running system will load its settings when they are referenced by the ConfigurationManager and keep the settings in memory for the rest of the session, meaning that if you edit the config-file, it will probably have no effect.

    If you want to use dynamic loading of the config file, you should consider declaring a custom section in the config file. You can of course put all your settings in the appSettings section, but then reloading your settings will be an all or nothing deal. The upside of using appSettings is that it is pre-defined (just like connectionStrings), meaning that you can access them directly from the ConfigurationManager. For your own sections, you need to declare your own variables to store the settings.

    This sample shows how to first register a section named featureToggle and then fill it with only one setting called Test. Remember that this is done in the main executable’s App.config file, no matter where the setting is read or used.

    <configuration>
    
      <configSections>
        <section name="featureToggle"
           type="System.Configuration.AppSettingsSection"/>
      </configSections>
    
      <featureToggle>
        <add key="Test" value="Test value is here!"/>
      </featureToggle>
    
    </configuration>

    A reference to System.Configuration is needed for the code to work.

    Like mentioned, if you were using the appSettings section, you could just run this line of code, and your settings will be reloaded:

    ConfigurationManager.RefreshSection("appSettings");

    But in this case, I am using a custom section called featureToggle, so I have to both refresh the section and save the section in my variable.

    ConfigurationManager.RefreshSection("featureToggle");
    
    _toggles = (NameValueCollection)ConfigurationManager
        .GetSection("featureToggle");

    The NameValueCollection is found in the System.Collections.Specialized namespace.

    If the _toggles (of type NameValueCollection) variable is initialized in a constructor of a custom configuration class, the constructor will contain the second of the two lines above. And both lines will be needed if you have some sort of reload method in the custom configuration class.

    The actual values in the config files are accessed via the _toggles variable, like so:

    public string Test
    {
        get
        {
            return _toggles["Test"];
        }
    }

    Good luck with your source code archeology work!

  • A JS implementation of the Harter-Heighway Dragon Curve

    Five years ago, I did a post in Swedish on the Harter-Heighway Dragon Curve, with C# source code. I have translated that code to JavaScript and added animation to the rendering. The fractal describes a way of folding a piece of paper so that its outline takes up a surface. Anyone who has read the book Jurassic Park has probably seen the fractal depicted.

    The repository is located here and a live demo is available here.

  • Return by reference

    Om inget annat anges skickas parametrar till funktioner “by value” i C#. Det innebär att den mottagande funktionen får en egen kopia av värdet som skickas. Att skicka “by reference” (nyckelordet ref) skiljer sig därmed, eftersom funktionen får en referens till ursprungsvariabeln. För en referensvariabel (som myValue i exemplet nedan) innebär “värdet” en referens till objektet av typen MyValue, vilket innebär att även om m (i MyFunction) är en kopia av myValue, så refererar både m och myValue till samma objekt på heapen. Så när den instansen ändras genom m i funktionen MyFunction, så slår det igenom på myValue.

    var myValue = new MyValue {X = 10};
    MyFuction(myValue);
    Console.WriteLine(myValue.X);
    
    void MyFuction(MyValue m)
    {
        m.X = 20;
    }
    
    public class MyValue
    {
        public int X { get; set; }
    }

    Resultatet av körningen blir alltså 20.
    Vill man se skillnaden mellan att skicka referenstyper “by value” och “by reference” så kan man tilldela ett nytt värde till själva variabeln (m). Eftersom värdet i m är en kopia av värdet i myValue, men myValue är en referensvariabel, kan vi sätta m till en ny referens utan att påverka ursprungsvariabeln myValue.

    var myValue = new MyValue {X = 10};
    MyFuction(myValue);
    Console.WriteLine(myValue.X);
    
    void MyFuction(MyValue m)
    {
        m = new MyValue {X = 20};
    }
    
    public class MyValue
    {
        public int X { get; set; }
    }

    Resultatet av körningen är 10, eftersom ursprungsvärdet inte förändrades. Variabeln m fick ett nytt objekt att referera till.
    Skulle däremot parametern skickas in “by reference”, skriver new över ursprungsvärdet i myValue, så att den pekar på det nya objektet.

    var myValue = new MyValue {X = 10};
    MyFuction(ref myValue);
    Console.WriteLine(myValue.X);
    
    void MyFuction(ref MyValue m)
    {
        m = new MyValue {X = 20};
    }
    
    public class MyValue
    {
        public int X { get; set; }
    }

    Resultatet av körningen blir därför 20.

    Funktioner som ger en retur, ger den “by value”, och det kan vara värt att tänka på om man hanterar större värdetyper. Koden nedan utför just ingenting, men trots att myValue skickas in “by reference” ska skapas en kopia vid return. Eftersom det som kopieras är en referensvariabel så är inte priset särskilt högt, men variabeln kopieras likväl.

    var myValue = new MyValue { X = 10 };
    myValue = MyFuction(ref myValue);
    Console.WriteLine(myValue.X);
    
    MyValue MyFuction(ref MyValue m)
    {
        return m;
    }
    
    public class MyValue
    {
        public int X { get; set; }
    }

    Precis som argument kan tas emot “by value” eller “by reference” kan även returer ske “by value” eller “by reference”, och precis som med argument så är det “by value” som gäller om inget annat sägs. Betrakta följande kod:

    var x = 10;
    var y = DoSomething(ref x);
    x++;
    Console.WriteLine(y);
    
    int DoSomething(ref int x)
    {
        x++;
        return x;
    }

    Rad 4 ger 11. Eftersom värdet kopieras på return-satsen, är det kopian som ökas till 12 på rad 3. Genom att skicka tillbaka värdet “by reference” får man 12:

    var x = 10;
    ref int y = ref DoSomething(ref x);
    x++;
    Console.WriteLine(y);
    
    ref int DoSomething(ref int x)
    {
        x++;
        return ref x;
    }

    Att skicka tillbaka värden “by reference” kan dels undvika att stora strukturer kopieras där det inte är önskvärt, och skulle t.ex. kunna användas för att låta annan kod manipulera ett element i en array. I detta exempel ger funktionen GiveElement ut ett element extern kod att manipulera.

    var arr = new[]{1,2,3,4,5};
    
    foreach (var i in arr)
        Console.WriteLine(i);
    
    ref int x = ref GiveElement(ref arr);
    
    x = 10;
    
    foreach (var i in arr)
        Console.WriteLine(i);
    
    ref int GiveElement(ref int[] arr)
    {
        return ref arr[3];
    }

    Andra gången arrayen skrivs ut, är värdet av det fjärde elementet (index 3) ändrat till 10.

    Del 1/2: Referenstyper eller värdetyper

  • Referenstyper eller värdetyper?

    Programmeringsspråket C# skiljer på referenstyper och värdetyper. I andra högnivåspråk, som t.ex. Java, har man fram till nyligen bara antagit att egna typer är referenstyper, men som C#-programmerare tvingas man då och då att tänka på skillnaden mellan att skapa egna referenstyper och värdetyper. Konsekvenser för användandet har förändrats en aning över olika versioner av C#, men den grundläggande skillnaden är fortfarande att referenstyper lägger sitt data på heapen medan värdetyper lägger sitt data på stacken.


    Det innebär att referenstyper är mer kapabla och mer flexibla, men mindre effektiva än värdetyper. Instanser av båda typerna lever alltså på stacken, men referenstyper heter som de gör för att de inte håller sitt data där – referenstypen håller en referens till sitt data som ligger någonstans på heapen.


    Traditionellt har man oftast valt att skapa egna typer som referenstyper i C#, för att de är mer kapabla, särskilt förr. Du har kunnat åstadkomma mer med nyckelordet class än med nyckelordet struct, men idag håller den skillnaden på att suddas ut. Hanteringen av null skiljer sig också mellan dessa, men även det håller på att unifieras, åtminstone på ytan. Tidigare fanns en enkel regel: Instanser av referenstyper var null tills de initierats, medan värdetyper hade ett (annat) defaultvärde tills de initierats. Numera förväntas man deklarera per variabel vilken som kan vara null.


    Av följande medlemsvariabler är a och b värdetyper, c och d referenstyper. Endast variabel a har ett värde som inte är null:


    private int a;
    private int? b;
    private string c;
    private string? d;

    Eftersom a är en värdetyp som inte är nullable, får den sitt default-värde som är 0. Eftersom b är en värdetyp som är nullable, får den sitt default-värde som är null. Både c och d är referenstyper och initieras därför till null, men kompilatorn kommer att bevaka att c får ett värde och att den hanteras som om den alltid är initierad, eftersom den inte är märkt som nullable.


    De typer du själv utvecklar bör vara referenstyper (class) om de är komposittyper av andra typer eller om datamängden variabel och gärna lite större. Du bör välja att skapa värdetyper (struct) om instanserna är kortlivade eller utgör en del av en komposittyp. Om instanserna är immutable kan det också vara värt att överväga att låta typen vara en värdetyp.

    Del 2/2: Return by reference

  • FileUtility PathInfo

    FileUtility PathInfo

    FileUtility är ett bibliotek för Windows/.NET 6.0 som (för närvarande) innehåller en enda klass: PathInfo. Den kan bl.a. korta ner en filsökväg för presentation på skärmen och ange fil- och katalogstorlek i flera format, inklusive ett människovänligt textformat. Exempelkod finns här.

    var max = 15;
    var f = new PathInfo(new FileInfo(@"C:\Temp\hello.txt"));
    Console.WriteLine(f.CompactPathForDisplay(15))
    
    // Result: C:...\hello.txt
  • Kolla tärningsformationer

    Kolla tärningsformationer

    Har du någon gång suttit och stirrat på 5-6 tärningar och funderat på vilka formationer ögonen utgör? Nu behöver du inte fundera längre, för här är en C#-klass som ger dig svaret på nolltid! Mycket nöje!

    Kodexempel: https://github.com/Anders-H/DiceFormationChecker

    Ladda hem: https://www.nuget.org/packages/Winsoft.Gaming.DiceFormationChecker/

  • Sprite (OpenGL i .NET Framework)

    Sprite (OpenGL i .NET Framework)

    Jag har dragit nytta av ett gammalt C#-repository som wrappar OpenGL för .NET Framework för att bygga ett minimalistiskt spelramverk. Följande kod visar stjärnor som flyger över skärmen, från höger till vänster. Projektet är .NET Framework 4.8 som jag valde för att det finns inbyggt Windows 10/11.

    Exemplet visar initiering av spelmotorn, att skapa sprites med GDI+, användning av en scen, tangentbordsavläsning, en sprite batch (som erbjuder fire-and-forget sprites).

    Projekttypen är en console application, och referenserna är Sprite och System.Drawing. Repositoryt finns här och detta är den kompletta koden:

    using System;
    using System.Drawing;
    using Sprite;
    
    namespace Starfield
    {
        public class Program
        {
            public static SpriteBitmap Star;
            public static GameEngine GameEngine { get; private set; }
    
            public static void Main()
            {
                // Create and initialize the game engine.
                GameEngine = new GameEngine("Starfield", OnLoadResources);
                GameEngine.LoadResources();
    
                // Run the game.
                GameEngine.Run(new StarfieldScene());
            }
    
            private static void OnLoadResources(object sender, EventArgs e)
            {
                // Draw a "star".
                var star = new Bitmap(2, 2);
                star.SetPixel(0, 1, Color.White);
                star.SetPixel(0, 0, Color.White);
                star.SetPixel(1, 1, Color.White);
                star.SetPixel(1, 0, Color.White);
    
                // Create a sprite bitmap from the star.
                Star = SpriteBitmap.FromImage(star, 1, 1);
    
                // Tell engine that loading is completed.
                GameEngine.SetLoadResourcesComplete();
            }
        }
    
        // Define the star sprite that will fly from right to left.
        public class StarSprite : BatchGameSprite
        {
            private int SpeedX { get; }
    
            public StarSprite(SpriteBitmap spriteBitmap, int y, int speedX) : base(spriteBitmap, 320, y, 1)
            {
                SpeedX = speedX;
            }
    
            public override void ApplyLogic() =>
                X += SpeedX;
    
            public override bool Alive() =>
                X > -5;
        }
    
        public class StarfieldScene : IScene
        {
            private readonly Random _rnd = new Random();
    
            // Create a sprite batch (used for fire-and-forget sprites) to hold the stars.
            private readonly SpriteBatch _spriteBatch = new SpriteBatch();
    
            public void Render(GameEngine gameEngine)
            {
                // Check if the user wants to exit.
                if (gameEngine.SpriteWindow.IsKeyDown(VirtualKeys.Escape))
                    gameEngine.SpriteWindow.Running = false;
    
                // Add a new star each frame.
                _spriteBatch.Add(new StarSprite(Program.Star, _rnd.Next(199), -(_rnd.Next(5) + 1)));
    
                // Make the stars act.
                _spriteBatch.ApplyLogic();
    
                // Draw the frame.
                _spriteBatch.Draw(gameEngine.SpriteWindow);
                gameEngine.SpriteWindow.Swap();
            }
        }
    }

  • Mutable strings i C#

    Mutable strings i C#

    Den strängtyp som finns inbyggd i C# är “immutable”, vilket betyder att den inte kan ändra värde. Jag har skapat en “mutable string”, som kapslar strängtypen i en klass. Det innebär att man kan anropa funktioner på strängen för att få den att ändra värde. Dessa funktioner, och funktionerna för att analysera strängens innehåll, har färdiga implementationer, men dessa kan ersättas. T.ex. kan funktionen Is svara på frågan om två strängar har samma värde, enligt de flaggor som angavs när strängen skapades, eller så kan man skicka med egen kod som utför jämförelsen som argument till funktionen Is. Mer information finns här: https://github.com/Anders-H/MutableStringLibrary

  • Input parser för textäventyrsspel

    Input parser för textäventyrsspel

    Biblioteket TextAdventureGameInputParser kan användas för att parsa användarens input i ett textäventyr. Parsern tar en mening i stil med GO NORT, OPEN DOOR, USE GOLD KEY ON DOOR eller GIVE FOOD TO GANDALF och ger en struktur med ordklasser och referenser till objekt som är registrerade i parsern.

    Lite exempel finns tillgängliga i parserns GitHub-repository, och parsern kan installeras (.NET 5.0) från Nuget.

  • Lorenz vattenhjul

    Lorenz vattenhjul

    Lorenz-attraktionen är en fraktal vars formel beskriver en rotationshastighet (illustrerat med en radie) och en rotationsriktning. Så här ser den ut, implementerad i Commodore BASIC 7.0:

    10 GRAPHIC 1,1
    20 X=5
    30 Y=5
    40 Z=5
    50 T=0
    60 S=1/200
    70 D=10
    80 R=28
    90 B=8/3
    100 T=T+0.1
    110 DX=D*(Y-X)
    120 X1=X+DX*S
    130 DY=(R*X-Y)-X*Z
    140 Y1=Y+DY*S
    150 DZ=X*Y-B*Z
    160 Z1=Z+DZ*S
    170 X=X1
    180 Y=Y1
    190 Z=Z1
    200 DRAW 1,150+4*X,20+3*Z
    210 IF T<1000 GOTO 100

    Det går att bygga ett riktigt vattenhjul som ger samma figur som algoritmen ovan. Tänk dig ett hjul med ett antal hinkar (t.ex. åtta stycken). Den hink som är högst upp fylls på med vatten, och att du ger hjulet en knuff så att det roterar åt höger. På det viset kommer nästa hink strax börja fyllas med vatten, vilket ger en vikt på hjulets högra sida, så att rotationshastigheten ökar. Men alla hinkar har ett hål i botten, så när de inte ökar i vikt för att de fylls med vatten, så minskar de i vikt. När hinkarna når den uppåtgående vänstersidan av hjulet, väger de mycket mindre, vilket bidrar till att rotationshastigheten ökar. Men om rotationshastigheten ökar, så minskar samtidigt mängden vatten som fylls på i hinken högst upp, eftersom hinken befinner sig kortare tid vid positionen för påfyllning. Det innebär att den tyngsta sidan inte alltid kommer vara högersidan, eftersom den höga rotationshastigheten och den låga påfyllningen får vikten att förflytta sig. Ibland roterar alltså hjulet åt höger, ibland åt vänster. Ibland roterar hjulet fort, ibland långsamt.

    Centralt i implementationen av denna simulering är en funktion som kan omvandla en vinkel på hjulet till en rotationskraft. Högst upp eller längst ner på hjulet, kommer vikten inte att påverka hjulets vilja att rotera alls. Längst till höger eller längst till vänster är kraften som störst. Vid 0 grader och vid 180 grader ska kraften vara 0, vid 90 och 270 grader är kraften maximal. Följande funktion är central för implementationen. Den beskriver hur starkt grepp gravitationen har över hinken – ingen alls högst upp eller längst ner, väldigt mycket längst till höger eller vänster.

    Math.Cos(angle / (180.0 / Math.PI));

    Implementationen av hjulet har 8 entiteter (“hinkar”) utplacerade med jämna mellanrum. 8 genom 350 ger 45 graders mellanrum. Den lilla knuffen åt höger får ha värdet 0,2 så att rotationen kommer igång.

    var angle = 0;
    for (var i = 0; i < 8; i++)
    {
        Wheel.Buckets.Add(new Bucket(Wheel, angle));
        angle += 45;
    }
    Wheel.Speed = 0.2;

    För hjulets beteende gäller följande: Hastigheten (som är positiv för höger och negativ för vänster) adderas till vinkeln. Därefter kontrollerar vi att vinkeln ligger mellan 0 och 360. Därefter berättar vi för varje hjul vilken vinkel de sitter på, givet hjulets vinkel, och ber samtidigt hjulet att agera (nedan). Sedan räknar vi ut hur hinkkonfigurationen påverkar hastigheten, och kontrollerar samtidigt att maxhastigheten åt höger eller vänster inte överstiger hjulets tänkta maxhastighet.

    public void Tick()
    {
        Angle += Speed;
        if (Angle < 0.0)
            Angle += 360.0;
        else if (Angle > 360.0)
            Angle -= 360.0;
    
        var bucketAngle = Angle;
    
        for (var i = 0; i < 8; i++)
        {
            if (bucketAngle < 0.0)
                bucketAngle += 360.0;
            else if (bucketAngle > 360.0)
                bucketAngle -= 360.0;
                
            Buckets[i].Tick(bucketAngle);
            bucketAngle += 45.0;
        }
        Speed += SpeedInfluence();
        const double maxSpeed = 7.0;
        if (Speed > maxSpeed)
            Speed = maxSpeed;
        else if (Speed < -maxSpeed)
            Speed = -maxSpeed;
    }
    
    public double SpeedInfluence() =>
        Buckets.Sum(x => x.SpeedInfluence());

    För varje hinks beteende gäller följande: Om hinken är högst upp, öka vattenmängden, annars minska vattenmängden en aning. Varje hjul måste kunna uppge gravitationens grepp om hinken.

    public void Tick(double newAngle)
    {
        Angle = newAngle;
            
        if (Angle >= 255.0 && Angle <= 285.0)
            Full += 2.0;
    
        Full -= 0.12;
            
        if (Full < 0.0)
            Full = 0.0;
        else if (Full > 100.0)
            Full = 100.0;
    }
    
    public double SpeedInfluence() =>
        Math.Cos(Angle / (180.0 / Math.PI)) * (Full * 0.03);

    Endast vattnet har vikt, hinkarna väger ingenting. Eftersom hjulet är balanserat och alla tomma hinkar väger lika mycket, kan vikten lika gärna vara 0.

    Resultatet blir ett hjul som ibland snurrar fort, ibland långsamt, ibland åt höger och ibland åt vänster.

    Koden körs i ett Windows Forms-fönster med DoubleBuffering aktiverat. Här är hela källkoden:

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Windows.Forms;
    
    public partial class Form1 : Form
    {
        private Wheel Wheel { get; }
    
        public Form1()
        {
            InitializeComponent();
            Wheel = new Wheel();
            InitializeWheel();
        }
    
        private void InitializeWheel()
        {
            var angle = 0;
            for (var i = 0; i < 8; i++)
            {
                Wheel.Buckets.Add(new Bucket(angle));
                angle += 45;
            }
            Wheel.Speed = 0.2;
        }
    
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            var g = e.Graphics;
            g.Clear(Color.Green);
    
            var centerX = (Bounds.Width / 2);
            var centerY = (Bounds.Height / 2);
                
            var availableSize = Bounds.Width > Bounds.Height
                ? Bounds.Height
                : Bounds.Width;
    
            var radius = availableSize * 0.3;
    
            var renderer = new Renderer(centerX, centerY, (float)radius);
            renderer.Draw(g, Wheel, Font);
        }
    
        private void timer1_Tick(object sender, EventArgs e)
        {
            Wheel.Tick();
            Invalidate();
        }
    }
    
    public class Wheel
    {
        public List<Bucket> Buckets { get; set; }
        public double Speed { get; set; }
        public double Angle { get; set; }
    
        public Wheel()
        {
            Buckets = new List<Bucket>();
            Angle = 0.0;
        }
    
        public void Tick()
        {
            Angle += Speed;
            if (Angle < 0.0)
                Angle += 360.0;
            else if (Angle > 360.0)
                Angle -= 360.0;
    
            var bucketAngle = Angle;
    
            for (var i = 0; i < 8; i++)
            {
                if (bucketAngle < 0.0)
                    bucketAngle += 360.0;
                else if (bucketAngle > 360.0)
                    bucketAngle -= 360.0;
                
                Buckets[i].Tick(bucketAngle);
                bucketAngle += 45.0;
            }
            Speed += SpeedInfluence();
            const double maxSpeed = 7.0;
            if (Speed > maxSpeed)
                Speed = maxSpeed;
            else if (Speed < -maxSpeed)
                Speed = -maxSpeed;
        }
    
        public double SpeedInfluence() =>
            Buckets.Sum(x => x.SpeedInfluence());
    }
    
    public class Bucket
    {
        public double Weight { get; set; }
        public double Angle { get; set; }
        public double Full { get; set; }
    
        public Bucket(double angle)
        {
            Weight = 0.0;
            Angle = angle;
            Full = 0.0;
        }
    
        public void Tick(double newAngle)
        {
            Angle = newAngle;
            
            if (Angle >= 255.0 && Angle <= 285.0)
                Full += 2.0;
    
            Full -= 0.12;
            
            if (Full < 0.0)
                Full = 0.0;
            else if (Full > 100.0)
                Full = 100.0;
        }
    
        public double SpeedInfluence() =>
            Math.Cos(Angle / (180.0 / Math.PI)) * (Full * 0.03);
    
        public void Draw(Graphics g, PointF location, Font font)
        {
            using var bucketBrush = new SolidBrush(Color.FromArgb(255, 255, 255));
            using var waterBrush = new SolidBrush(Color.FromArgb(0, 128, 255));
            var bucketRectangle = new RectangleF(location.X - 51, location.Y - 51, 102, 102);
            g.FillRectangle(bucketBrush, bucketRectangle);
            if (Full > 0.0)
            {
                var waterRectangle = new RectangleF(location.X - 50, (float)(location.Y - 50 + (100.0 - Full)), 100, (float)Full);
                g.FillRectangle(waterBrush, waterRectangle);
            }
            g.DrawString(Angle.ToString("0.00"), font, Brushes.Black, (float)(location.X - 20), (float)(location.Y - 20));
            g.DrawString(Full.ToString("0.00"), font, Brushes.Black, (float)(location.X - 20), (float)(location.Y));
        }
    }
    
    public class Renderer
    {
        private readonly PointF _center;
        private readonly float _radius;
    
        public Renderer(float centerX, float centerY, float radius)
        {
            _center = new PointF(centerX, centerY);
            _radius = radius;
        }
    
        public void Draw(Graphics g, Wheel wheel, Font font)
        {
            foreach (var bucket in wheel.Buckets)
            {
                var bucketLocation = new PointF(
                    (float)(_center.X + Math.Cos(bucket.Angle / (180.0 / Math.PI)) * _radius),
                    (float)(_center.Y + Math.Sin(bucket.Angle / (180.0 / Math.PI)) * _radius)
                );
                g.DrawLine(Pens.Black, _center, bucketLocation);
                bucket.Draw(g, bucketLocation, font);
            }
        }
    }

    Det går garanterat att justera acceleration, gravitation, inflödeshastighet, utflödeshastighet och maximal hastighet för andra (bättre?) resultat.

  • GetEnumerator extension method

    GetEnumerator extension method

    En ganska enkel men ytterst trevligt tillägg i C# version 9 är möjligheten att skapa en extension method av GetEnumerator (som konsulteras när en samling ska enumereras). Betrakta denna lilla lista:

    var people = new List<string>
    {
        "Sven",
        "Nils",
        "Niklas",
        "Janne"
    };

    Om jag skulle vilja ha samtliga poster i listan för utskrift på skärmen, kan denna kod användas:

    foreach (var p in people)
        Console.WriteLine(p);

    GetEnumerator används implicit. Men om jag bara är intresserad av att ha med poster vars förnamn börjar på N, så måste det formuleras någonstans. Jag kan t.ex. kopiera listan och exkludera ointressanta poster, eller så skulle jag kunna iterera genom svaret från LINQ-API:et enligt:

    foreach (var p in people.Where(x => x.StartsWith("N")))
        Console.WriteLine(p);

    Extension methods har fördelen att de är tillgängliga i filer som läst in dess namnrymd, men är i övrigt otillgängliga. Det innebär att man kan begränsa funktionalitet till vissa filer genom att skapa en extension metod. Om jag generellt sett är intresserad av att ha personerna som börjar på N tillgängliga i den aktuella filen (eller på annat sätt kunna använda manipulerat data) kan jag deklarera en egen GetEnumerator som en extension method. Är den tillgänglig, kommer kompilatorn att välja den. I detta fall kommer man troligtvis att vilja ha full tillgänglighet för metoden.

    static class MyEnumerator
    {
        public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> list) =>
            list;
    }

    Med denna på plats, öppnas en ny möjlighet. Svaret från den funktionen, kan itereras. Nu kan jag skapa en enumerator som t.ex. skickar tillbaka personerna som börjar på N. Med det gjort, blir iterationen lika enkel som i första exemplet, fast med den skillnaden att jag nu kontrollerar dess innehåll.

    
    var somePeople = people
       .Where(x => x.StartsWith("N"))
       .GetEnumerator();
    
    foreach (var p in somePeople)
        Console.WriteLine(p);

    Detta är egentligen inte en stor sak, men det bidrar till kodens elegans – något som har blivit ett av kännetecknen för C#.

  • Objektnotation: XML, JSON, PSON

    Objektnotation: XML, JSON, PSON

    Här följer en kort kommentar om tre olika format för textbaserad objektnotation.

    XML

    Fördelar: Stöd för dokumenttypsdefinitioner (DTD) och scheman. God tillgång på bra API:er.
    Nackdelar: Mycket overhead (ett “pratigt” språk), endast Visual Basic har inbyggt stöd för formatet.

    Exempel på notation:

    <?xml version="1.0" encoding="utf-8" ?>
    <customer>
    	<id>551</id>
    	<account>9153</account>
    	<name>
    		<firstName>Sven</firstName>
    		<lastName>Hedin</lastName>
    	</name>
    </customer>

    Exempel på inläsning (C#, otypat):

    var dom = new XmlDocument();
    dom.Load("object.xml");
    var document = dom.DocumentElement;
    var customerId = document!.SelectSingleNode("id")!.InnerText;
    Console.WriteLine($"ID={customerId}");

    JSON

    Fördelar: Liten overhead, god tillgång på bra API:er. Minimal schematisk information (vad som är en array eller ett enskilt objekt) kan anges i formatet.
    Nackdelar: Nästan ingen typsäkerhet.

    Exempel på notation:

    {
      "id": 551,
      "account": 9153,
      "name": {
        "firstName": "Sven",
        "lastName":  "Hedin" 
      } 
    }

    Exempel på inläsning (C#, otypat):

    using var sr = new StreamReader("object.json", Encoding.UTF8);
    var json = sr.ReadToEnd();
    dynamic customer = JObject.Parse(json);
    Console.WriteLine($"ID={customer!.id}");

    Exempel på inläsning (C#, typat):

    using var sr = new StreamReader("object.json", Encoding.UTF8);
    var json = sr.ReadToEnd();
    var customer = JsonSerializer.Deserialize<Customer>(json);
    Console.WriteLine($"ID={customer!.Id}");
    
    class Customer
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }
        [JsonPropertyName("account")] 
        public int Account { get; set; }
        [JsonPropertyName("name")] 
        public Name Name { get; set; }
    }
    
    class Name
    {
        [JsonPropertyName("firstName")]
        public string FirstName { get; set; }
        [JsonPropertyName("lastName")]
        public string LastName { get; set; }
    }

    PSON

    Fördelar: Liten overhead. Viss schematisk information kan anges i formatet, som vad som är en array eller ett enskilt objekt och objekttyp. Kan skrivas typsäkert.
    Nackdelar: Kan endast användas från PowerShell.

    Exempel på notation:

    @{
      Id: 551,
      Account: 9153,
      Name: @{
        FirstName: "Sven"
        FastName:  "Hedin" 
      } 
    }

    Inläsning av den filen sker genom PowerShell och kan plockas upp av C# genom det generella gränssnittet Microsoft tillhandahåller för att nå svaret från PowerShell.

    Datastorleken för XML är 188 bytes, JSON 110 bytes och PSON 104 bytes.

  • C# 9 Records

    C# 9 Records

    Hur projektmallarna pytsas ut i Visual Studio har verkligen övergått mitt förstånd. Beträffande .NET 5.0 var Windows Forms-mallen tillgänglig långt innan Console Application-mallen, men nu är äntligen allt på plats. Med anledning av det vill jag summera nyheterna i C# version 9 som, för att fungera fullt ut, kräver .NET 5.0. Här är den första:

    Records
    Records är klasser där man gjort avkall på vissa möjligheter för att komma åt möjligheten skapa objekt med properties på så lite kod som möjligt. Nyckelordet record anger att de medlemmar som följer ska vara publika (vilket inte stämmer enligt nuvarande officiell version) och om de inte ska kunna modifieras i programkoden, kan de nu deklareras som init (vilket faktiskt fungerar), vilket betyder public fram till första tilldelningen, därefter private.

    using System;
    
    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var a = new Something {X = 10, Y = 20};
                Console.WriteLine(a.X);
            }
        }
    
        public record Something
        {
            public int X { get; init; }
            public int Y { get; init; }
        }
    }

    Skulle programmet fortsätta med tilldelningen a.X = 30 skulle kompilatorn påpeka att vår enda chans att lagra et värde i a.X är förbrukad.

  • Hur svårt är det att gissa ett tal?

    Hur svårt är det att gissa ett tal?

    En person tänker på ett tal mellan 1 och 100, och en annan ska gissa vilket. Om den som gissar skulle gissa fel, så får han reda på om han har gissat på ett för stort eller för litet tal. Den bästa strategin för att gissa rätt på så få försök som möjligt, är att försöka diskvalificera så många tal som möjligt.

    Genom att gissa på 10 och få höra att talet är för stort, är genast 91 tal diskvalificerade, och rätt svar måste vara mellan 1 och 9, men man kan lika gärna (och troligen) få höra att talet är för litet, och då är bara 10 tal (1 till 10) diskvalificerade medan 90 tal fortfarande kan vara aktuella.

    Smartast är att gissa på 50. Är det fel svar, diskvalificerar det ändå fler än hälften av alla tal mellan 1 och 100, oberoende av om 50 är för stort eller inte. Skulle det vara för stort gissar man på 25, skulle det vara för litet gissar man på 75, och så vidare. Den vinnande strategin för den som gissar på ett tal är alltså att dra av hälften av talrymdens storlek och addera det till eller dra bort det ifrån senaste gissning.

    Eftersom det finns en bästa strategi för den som gissar, så kommer antalet försök att hitta ett tal alltid bli detsamma för ett specifikt tal. Personen tänker på talet 50, kommer den som gissar att hitta talet på första försöket. För att mäta hur lång tid det tar att hitta ett specifikt tal, och för att slippa sitta med en vän och gissa på tal hela veckan, har jag skapat två enkla robotar.

    Roboten Conny kan tänka på ett tal och be någon att gissa. Roboten Steven kan gissa på tal, och är dessutom bekant med gissningsstrategin jag beskrev ovan. Detta är Conny:

    public class NumberThinkingRobot
    {
        private readonly ICanGuess _guesser;
        private readonly int _correct;
    
        public NumberThinkingRobot(int correct, ICanGuess guesser)
        {
            _correct = correct;
            _guesser = guesser;
        }
    
        public void Begin()
        {
            var tryCount = 0;
            do
            {
                Console.WriteLine($"Try no. {++tryCount}");
    
                var guess = _guesser.Guess();
    
                if (guess < _correct)
                    _guesser.TooSmall();
                else if (guess > _correct)
                    _guesser.TooLarge();
                else
                {
                    Console.WriteLine("Correct!");
                    break;
                }
            } while (true);
        }
    }

    Och detta är Steven:

    public class NumberGuessingRobot : ICanGuess
    {
        private readonly int _max;
        private int _correct;
        private double _stepSize;
    
        public NumberGuessingRobot(int max)
        {
            _max = max;
            _correct = _max/2;
            _stepSize = _correct;
        }
    
        public int Guess()
        {
            Console.WriteLine($"Guess: {_correct}");
            return _correct;
        }
    
        public void TooSmall()
        {
            Console.WriteLine("Too small!");
    
            _stepSize /= 2.0;
            _stepSize = _stepSize < 1
                ? 1
                : _stepSize;
    
            _correct += (int)Math.Round(_stepSize);
            _correct = _correct > _max
                ? _max
                : _correct;
        }
    
        public void TooLarge()
        {
            Console.WriteLine("Too large!");
    
            _stepSize /= 2.0;
            _stepSize = _stepSize < 1
                ? 1
                : _stepSize;
    
            _correct -= (int)Math.Round(_stepSize);
            _correct = _correct < 1
                ? 1
                : _correct;
        }
    }

    Programmet presenteras i sin helhet sist. Så låt oss instruera Conny att det korrekta talet är 22, och instruera Steven att det högsta tillåtna talet är 100, för att sedan fösa ihop dem.

    using System;
    
    const int max = 100;
    const int correct = 22;
    var steven = new NumberGuessingRobot(max);
    var conny = new NumberThinkingRobot(correct, steven);
    conny.Begin();

    Vi kan konstatera att Steven behöver ha fem försök för att hitta 22. Detta är resultatet av körningen:

    Try no. 1
    Guess: 50
    Too large!
    Try no. 2
    Guess: 25
    Too large!
    Try no. 3
    Guess: 13
    Too small!
    Try no. 4
    Guess: 19
    Too small!
    Try no. 5
    Guess: 22
    Correct!

    Så hur svåra är de olika talen mellan 1 och 100 att hitta? 50 hittas direkt och 25 och 75 hittas på två gissningar. 13, 37, 63 och 87 kräver tre gissningar. 7 19, 31, 43, 57, 69, 81 och 93 kräver fyra. För 4, 10, 16, 22, 28, 34, 40, 46, 54, 60, 66, 72, 78, 84, 90 och 96 krävs fem gissningar. Resterande sextionio tal kräver sex gissningar eller fler, vilket innebär att det i snitt tar nästan sex gissningar att hitta rätt med den bästa strategin.

    Om man istället ska gissa på ett tal mellan 1 och 1000 så har vi tio gånger så många alternativ till korrekt tal, men strategin kräver bara i snitt nio gissningar. Om vi har hundra gånger så många alternativ (ett tal mellan 1 och 10.000) krävs i snitt ungefär 12 gissningar, och för att gissa ett tal mellan 1 och 100.000 krävs knappt 16 gissningar.

    Här är programmet i sin helhet (C#9):

    using System;
    
    const int max = 100;
    const int correct = 22;
    var steven = new NumberGuessingRobot(max);
    var conny = new NumberThinkingRobot(correct, steven);
    conny.Begin();
    
    public class NumberThinkingRobot
    {
        private readonly ICanGuess _guesser;
        private readonly int _correct;
    
        public NumberThinkingRobot(int correct, ICanGuess guesser)
        {
            _correct = correct;
            _guesser = guesser;
        }
    
        public void Begin()
        {
            var tryCount = 0;
            do
            {
                Console.WriteLine($"Try no. {++tryCount}");
    
                var guess = _guesser.Guess();
    
                if (guess < _correct)
                    _guesser.TooSmall();
                else if (guess > _correct)
                    _guesser.TooLarge();
                else
                {
                    Console.WriteLine("Correct!");
                    break;
                }
            } while (true);
        }
    }
    
    public class NumberGuessingRobot : ICanGuess
    {
        private readonly int _max;
        private int _correct;
        private double _stepSize;
    
        public NumberGuessingRobot(int max)
        {
            _max = max;
            _correct = _max/2;
            _stepSize = _correct;
        }
    
        public int Guess()
        {
            Console.WriteLine($"Guess: {_correct}");
            return _correct;
        }
    
        public void TooSmall()
        {
            Console.WriteLine("Too small!");
    
            _stepSize /= 2.0;
            _stepSize = _stepSize < 1
                ? 1
                : _stepSize;
    
            _correct += (int)Math.Round(_stepSize);
            _correct = _correct > _max
                ? _max
                : _correct;
        }
    
        public void TooLarge()
        {
            Console.WriteLine("Too large!");
    
            _stepSize /= 2.0;
            _stepSize = _stepSize < 1
                ? 1
                : _stepSize;
    
            _correct -= (int)Math.Round(_stepSize);
            _correct = _correct < 1
                ? 1
                : _correct;
        }
    }
    
    public interface ICanGuess
    {
        int Guess();
        void TooSmall();
        void TooLarge();
    }
  • C# from the command window

    C# from the command window

    If you are using .NET Core (or .NET 5.0) you can easily activate the ability to run C# code from the command window (Windows Terminal or the old cmd.exe).

    To get started, install dotnet-script by typing:

    dotnet tool install --global dotnet-script

    Once installed, you can start the dotnet-script program just by typing:

    dotnet-script

    The prompt now looks like a greater than-sign (>). From here, you can type any C# code and you can reference anything within the System namespace. For example, if you type:

    Console.WriteLine("Hello world!");

    …you will get the following answer:

    Hello world!

    A few things to keep in mind: Types you add will be remembered for the whole session. For example, if you type in a class, you can use that class in the session. And if the command windows expect further input, the prompt will change from a greater than-signe to an asterisk sign (*). So if you type in something like class MyClass { and press Enter, nothing will be executed. Instead, the command window will indicate that more input is expected by displaying an asterisk sign. To try this out, type something like this (terminating each statement with Enter):

    class MyClass {
    public void DoSomething() {
    Console.WriteLine("Hello!");
    }
    }

    And then, you can use your new type like this:

    new MyClass().DoSomething();

    The response will be:

    Hello!

    The picture shows the different prompts when you type in a simple for loop:

    This is a very powerful tool to use when you want to perform tasks that are a bit too complicated for a calculator, but perhaps a bit too simple fore an advanced computer program, or when you want to try out some API you are working on. Happy programming!

  • DOD är bättre OOD för tidskritiska system

    DOD är bättre OOD för tidskritiska system

    Objektorienterad design (OOD) är ofta ett bra val för att det är relativt enkelt att använda med robusta mönster att följa. Men när man programmerar tidskritiskt, som t.ex. i när man gör spel, kan det vara värt att titta på dataorienterad design (DOD).

    DOD handlar om att utnyttja processorns cache. Normalt när man läser och skriver data ber man processorn att läsa och skriva från RAM-minnet. Men när så sker, antar processorn att fler läs- och skrivoperationer kommer att ske med närliggande minneadresser, och cachar således närliggande data. Man kan alltså öka prestandan i sitt program, genom att se till att data som uppdateras ofta ligger nära varandra i minnet.

    Jag har en väldigt enkel dator, en liten NUC från ASUS. Jag har skrivit ett litet testprogram i C#/.NET Core 3.1 som skapar 500 rymdskepp och flyttar dessa en halv miljon enheter i sidled, både enligt OOD och DOD.

    Den objektorienterade lösningen

    Denna kod definierar ett rymdskepp enligt traditionell OOD:

    public class Spaceship
    {
        public int X { get; set; }
        public int Y { get; set; }
    }

    Följande lilla kodsnutt skapar 500 rymdskepp i en array (givet att konstanten Ships är satt till 500):

    var spaceshipsOop = new Spaceship[Ships];
    
    for (var i = 0; i < Ships; i++)
        spaceshipsOop[i] = new Spaceship
        {
            X = 0,
            Y = i
        };

    Och denna kod förflyttar alla 500 skepp en halv miljon enheter åt vänster (eftersom konstanten travelDistance är satt till 500 000):

    for (var x = 0; x < travelDistance; x++)
        for (var i = 0; i < Ships; i++)
            spaceshipsOop[i].X++;

    Hela kalaset kostar 194 millisekunder (3,124 sekunder i debugläge) för min lilla processor att utföra.

    Den dataorienterade lösningen

    I den dataorienterade lösningen vill vi försäkra oss om att data som kommer uppdateras samtidigt också finns lagrat tillsammans. Därför skulle definitionen av ett rymdskepp kunna bytas ut mot en definition av samtliga rymdskepp:

    public class Spaceships
    {
        public int[] X { get; set; }
        public int[] Y { get; set; }
    }

    Det innebär att koden som skapar 500 rymdskepp istället ser ut så här:

    var spaceshipsDoa = new Spaceships
    {
        X = new int[Ships], Y = new int[Ships]
    };
    for (var i = 0; i < Ships; i++)
    {
        spaceshipsDoa.X[i] = 0;
        spaceshipsDoa.Y[i] = i;
    }

    Och slutligen, koden som förflyttar skeppen en halv miljon enheter åt vänster, ser nu ut så här:

    for (var x = 0; x < travelDistance; x++)
        for (var i = 0; i < Ships; i++)
            spaceshipsDoa.X[i]++;

    På min enhet har processorn fått jobba i 334 millisekunder (1,419 sekunder i debug-läge) för att åstadkomma detta, vilket är en rejäl försämring, men en förbättring på mer än 50 procent i debug-läge.

    Vi kan alltså konstatera att man inte har någon automatisk vinst innan man kommer upp i lite tyngre arbeten, och det är därför fördelen med DOD var så tydligt i debug-läge. I release-läge ligger break-even för min del när 500 skepp gör en resa på två miljoner enheter (ungefär 1,2 sekunder oavsett strategi), och vid fem miljoner enheter kostar OOD-lösningen 3,2 sekunder och DOD-lösningen 2,5 sekunder.

    Fördelen med DOD minskar ytterligare om antalet rymdskepp som ska förflyttas minskar, men ökar om antalet rymdskepp som ska förflyttas ökar. Med 2000 rymdskepp som ska som ska förflyttas fem miljoner enheter kostar OOD-lösningen 100 sekunder och DOD-lösningen 65 sekunder, vilket är en klart märkbar förbättring. Om en skälig mängd data ska användas väldigt intensivt och prestanda är kritiskt så är DOD att föredra, men i övrigt spelar det ingen större roll

    Hela källkoden (C# version 8.0, .NET Core version 3.1):

    using System;
    using System.Diagnostics;
    
    namespace ConsoleApp2
    {
        public class Program
        {
            public const int Ships = 2000;
    
            private static void Main()
            {
                const int travelDistance = 5000000;
    
                var spaceshipsOop = new Spaceship[Ships];
    
                for (var i = 0; i < Ships; i++)
                    spaceshipsOop[i] = new Spaceship
                    {
                        X = 0,
                        Y = i
                    };
    
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                
                for (var x = 0; x < travelDistance; x++)
                    for (var i = 0; i < Ships; i++)
                        spaceshipsOop[i].X++;
    
                stopwatch.Stop();
    
                Console.WriteLine($"Object oriented approach: {stopwatch.ElapsedMilliseconds}");
    
                stopwatch.Reset();
    
                var spaceshipsDoa = new Spaceships
                {
                    X = new int[Ships], Y = new int[Ships]
                };
                for (var i = 0; i < Ships; i++)
                {
                    spaceshipsDoa.X[i] = 0;
                    spaceshipsDoa.Y[i] = i;
                }
    
                stopwatch.Start();
    
                for (var x = 0; x < travelDistance; x++)
                    for (var i = 0; i < Ships; i++)
                        spaceshipsDoa.X[i]++;
    
                stopwatch.Stop();
    
                Console.WriteLine($"Data oriented approach: {stopwatch.ElapsedMilliseconds}");
            }
        }
    
        public class Spaceship
        {
            public int X { get; set; }
            public int Y { get; set; }
        }
    
        public class Spaceships
        {
            public int[] X { get; set; }
            public int[] Y { get; set; }
        }
    }
  • Othello

    Othello

    Här är källkoden Othello, till ett enkelt strategispel för Windows-skrivbordet. Du vinner över datorn genom att ha se till att spelplanen innehåller fler svarta brickor än vita. Reglerna finns på Wikipedia.

    Uppdatering 2023-02-11: Installationsprogram för Windows 10/11 finns här.

  • Funktioner är andra klassens medborgare i C#

    Funktioner är andra klassens medborgare i C#

    I C# 8 är fortfarande funktioner ett slags “andra klassens medborgare” i jämförelse med variabler. Man kan alltså fortfarande göra mer med en variabel än en funktion i C# 8.

    Varken variabler eller funktioner kan skapas i namnrymder, båda kan skapas i en klass och båda kan skapas i en funktion, vilket illustreras av detta exempel:

    namespace ConsoleApp1
    {
        public class MyClass
        {
            public int VariableInClass;
    
            public void FunctionInClass()
            {
                int variableInFunction;
                
                void functionInFunction() { }
            }
        }
    }

    Eftersom det finns saker som du kan göra med variabler som du inte kan göra med funktioner, så är funktioner fortfarande att betrakta som ett slags andra klassens medborgare. Du kan inte lagra en funktion i en variabel, du kan inte skicka funktioner som argument och du kan inte använda type inference när du skapar funktioner.

    var x = 10;
    
    //Variabel som påverkar x
    Action y = () => x++;
    y();
    Console.WriteLine(x);
    
    //Funktion som påverkar x
    void z() => x++;
    z();
    Console.WriteLine(x);

    Att man inte kan lagra funktioner i variabler spelar inte någon jättestor roll. Som namnet antyder beskriver funktioner ofta något funktionellt, vilket i praktiken innebär att de innehåller programsatser. Programsatser kan även lagras i variabler, vilket innebär att funktionalitet kan lagras i en variabel och skickas som argument. Detta exempel visar hur instruktionen att summera två tal lagras i en variabel:

    //Add the ability to add
    //two integers in x.
    Func<int, int, int> x =
        (int a, int b) => a + b;
    
    //Execute the content
    //of x, save the result in y.
    var y = x(10, 20);

    Detta exempel visar hur samma instruktion skickas som parameter till en funktion:

    GiveMeFunctionality((a, b) => a + b);

    C#-programmets startpunkt kan inte ersättas av en variabel, utan måste vara en riktig funktion kallad Main. I vissa fall kan man kringgå att en funktion inte kan användas som en variabel genom att skapa en variabel som anropar en funktion, och skicka den istället. T.ex. så här:

    using System;
    
    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                GiveMeFunctionality(
                    Functionality);
            }
    
            static int Functionality
                (int a, int b) =>
                a + b;
            
            static void GiveMeFunctionality(
                Func<int, int, int> x)
            {
                Console.WriteLine(x(10, 20));
            }
        }
    }

    Men till skillnad från variabler kan funktioner vara rekursiva. En funktion kan alltid anropas från en annan funktion, och tack vara stödet för reflection i C# (som t.ex. C++ saknar) kan funktioner alltid anropas oberoende av var i koden de är deklarerade.

    var x = 0;
    void i()
    {
        x++;
        while (x < 10) 
            i();
    }

    Motsvarigheten skulle kunna vara något i den här stilen, vilket kräver att variabeln i finns medan den fortfarande deklareras:

    var x = 0;
    var i = () => {
        x++;
        while (x < 10) 
            i();
    };
  • Value tuples i C# 7.3

    Value tuples i C# 7.3

    En av många anledningar till att jag älskar C#: Språkligt stöd för value tuples!

  • Pathfinding i terräng

    Pathfinding i terräng

    A* (A-star) är en snabb pathfinder-algoritm som kan användas i t.ex. strategispel för att hitta vägar genom labyrinter eller terräng. Christoph Husse publicerade 2010 en briljant implementation i C# som tillåter egna kriterier, definierade i en solver, för kostnaden att flytta sig från en nod till en annan. I följande exempel har jag använt Christophs kod, men jag har skalat bort hans solver och istället hårdkodat in en viktad terräng. Kartan i exemplet är liten, endast 50×25 celler, och kostnaden för att gå på en cell sträcker sig från 0 till 99. De celler som har värdet 100 betraktas som ett oöverstigligt hinder. Bilden visar en körning, där startpositionen är längst ner till vänster.

    Det hela utspelar sig i ett vanligt Windows Forms-fönster som har en karta (typen Map) och en stig (typen Path). Både Map och Path presenteras nedan. (De första fyra kodblocken visar alltså innehållet i fönstret, och de resterande kodblocken visar klasserna som används i projektet.)

    private Map Map { get; set; }
    private Path Path { get; set; }

    I fönstrets Load-event skapar jag kartan, kartans terräng och använder sedan klassen som implementerar A* för att hitta den bästa vägen från start (längst ner till vänster) till mål (längst upp till höger). Funktionen FindPath ger null om det inte finns någon väg mellan punkterna.

    private void Form1_Load(object sender, EventArgs e)
    {
        Map = new Map(50, 25);
        CreateTerrain(Map);
        var aStar = new AStar(Map);
        Path = aStar.FindPath(
            new Point(0, Map.Height - 1),
            new Point(Map.Width - 1, 0))
            ?? new Path();
        Invalidate();
    }

    Funktionen för att generera terrängen, CreateTerrain, skapar ett godtyckligt bergslandskap.

    private void CreateTerrain(Map map)
    {
        var rnd = new Random();
        var walkcosts = new int[map.Width + 2, map.Height + 2];
        for (var y = 0; y < map.Height + 2; y++)
            for (var x = 0; x < map.Width + 2; x++)
                if (rnd.Next(0, 3) == 1)
                    walkcosts[x, y] = rnd.Next(-10, 300);
        int GetAverage(int[,] a, int x, int y) =>
            (a[x - 1, y - 1] + a[x, y - 1] + a[x + 1, y - 1]
             + a[x - 1, y] + a[x, y] + a[x + 1, y]
             + a[x - 1, y + 1] + a[x, y + 1] + a[x + 1, y + 1]) / 9;
        for (var y = 0; y < map.Height; y++)
        {
            for (var x = 0; x < map.Width; x++)
            {
                var avg = GetAverage(walkcosts, x + 1, y + 1);
                if (avg < 0)
                    avg = 0;
                else if (avg > 100)
                    avg = 100;
                map.SetWalkCost(x, y, (byte)avg);
            }
        }
    }

    I fönstrets Paint-event ritas det hela ut på skärmen.

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.Clear(Color.Black);
        var m = new MapRenderer(Map, Path, 24);
        m.Draw(e.Graphics, 0, 0);
    }

    Klassen MapRenderer som ansvarar för ritandet ser ut så här:

    using System.Drawing;
    using System.Drawing.Drawing2D;
    
    namespace PathFinderAStar
    {
        public class MapRenderer
        {
            public Map Map { get; }
            public Path Path { get; }
            public int CellSize { get; }
    
            public MapRenderer(Map map, Path path, int cellSize)
            {
                Map = map;
                Path = path;
                CellSize = cellSize;
            }
    
            public void Draw(Graphics g, int offsetX, int offsetY)
            {
                var xpos = offsetX;
                var ypos = offsetY;
                using (var font = new Font(FontFamily.GenericSansSerif, 8))
                {
                    for (var y = 0; y < Map.Height; y++)
                    {
                        for (var x = 0; x < Map.Width; x++)
                        {
                            g.SmoothingMode = SmoothingMode.None;
                            g.DrawRectangle(
                                Pens.Gray,
                                xpos,
                                ypos,
                                CellSize,
                                CellSize);
                            using (var b = new SolidBrush(
                                Color.FromArgb(Map.GetWalkCost(x, y) * 2,
                                0,
                                0)))
                                g.FillRectangle(
                                    b,
                                    xpos + 2,
                                    ypos + 2,
                                    CellSize - 3,
                                    CellSize - 3);
                            var pathCell = Path.GetCellAt(x, y);
                            g.SmoothingMode = SmoothingMode.HighQuality;
                            if (pathCell != null)
                                g.FillEllipse(
                                    Brushes.CadetBlue,
                                    xpos + 5,
                                    ypos + 5,
                                    CellSize - 9,
                                    CellSize - 9);
                            g.DrawString(
                                Map.GetWalkCost(x, y).ToString(),
                                font,
                                Brushes.White,
                                xpos + 1,
                                ypos + 1);
                            xpos += CellSize;
                        }
                        xpos = offsetX;
                        ypos += CellSize;
                    }
                }
            }
        }
    }

    Klassen Map beskriver en 2-dimensionell karta, på vilken vi ska hitta en stig.

    using System;
    using System.Diagnostics;
    using System.Drawing;
    
    namespace PathFinderAStar
    {
        public class Map
        {
            private readonly MapCell[,] _terrain;
            public int Width { get; }
            public int Height { get; }
    
            public Map(int width, int height)
            {
                if (width < 2 || width > 2000)
                    throw new ArgumentOutOfRangeException(nameof(width));
                if (height < 2 || height > 2000)
                    throw new ArgumentOutOfRangeException(nameof(height));
                Width = width;
                Height = height;
                _terrain = new MapCell[width, height];
            }
    
            public byte GetWalkCost(int x, int y)
            {
                if (x < 0 || x >= Width)
                    throw new ArgumentOutOfRangeException(nameof(x));
                if (y < 0 || y >= Height)
                    throw new ArgumentOutOfRangeException(nameof(y));
                var cell = _terrain[x, y];
                if (cell == null)
                    return 0;
                Debug.Assert(cell.X == x);
                Debug.Assert(cell.Y == y);
                return cell.WalkCost;
            }
    
            public void SetWalkCost(int x, int y, byte walkCost)
            {
                if (x < 0 || x >= Width)
                    throw new ArgumentOutOfRangeException(nameof(x));
                if (y < 0 || y >= Height)
                    throw new ArgumentOutOfRangeException(nameof(y));
                if (walkCost > 100)
                    throw new ArgumentOutOfRangeException(nameof(walkCost));
                var cell = _terrain[x, y];
                if (cell == null)
                {
                    cell = new MapCell(x, y, walkCost);
                    _terrain[x, y] = cell;
                    return;
                }
                Debug.Assert(cell.X == x);
                Debug.Assert(cell.Y == y);
                cell.WalkCost = walkCost;
            }
    
            public void Clear()
            {
                for (var y = 0; y < Height; y++)
                    for (var x = 0; x < Width; x++)
                        _terrain[x, y] = null;
            }
    
            public MapCell GetCell(int x, int y) =>
                GetCell(new Point(x, y));
    
            public MapCell GetCell(Point p)
            {
                if (p.X < 0 || p.X >= Width || p.Y < 0 || p.Y >= Height)
                    return null;
                var c = _terrain[p.X, p.Y];
                return c;
            }
    
            public double GetDistance(MapCell start, MapCell goal)
            {
                var actualDistance = Math.Sqrt((start.X - goal.X)
                    * (start.X - goal.X)
                    + (start.Y - goal.Y)
                    * (start.Y - goal.Y));
                return actualDistance + (double)goal.WalkCost / 10;
            }
    
            public void Add(MapCell cell)
            {
                if (cell == null)
                    throw new ArgumentNullException(nameof(cell));
                if (cell.X < 0 || cell.X >= Width)
                    throw new ArgumentOutOfRangeException(nameof(cell.X));
                if (cell.Y < 0 || cell.Y >= Height)
                    throw new ArgumentOutOfRangeException(nameof(cell.Y));
                if (_terrain[cell.X, cell.Y] != null)
                    throw new SystemException();
                _terrain[cell.X, cell.Y] = cell;
            }
    
            public void Remove(MapCell cell)
            {
                if (cell == null)
                    throw new ArgumentNullException(nameof(cell));
                if (_terrain[cell.X, cell.Y] == null)
                    throw new SystemException();
                if (_terrain[cell.X, cell.Y] != cell)
                    throw new SystemException();
                _terrain[cell.X, cell.Y] = null;
            }
    
            public bool Contains(MapCell cell) =>
                _terrain[cell.X, cell.Y] != null;
    
            public void SetWithHistory(int x, int y, MapCell cell) =>
                _terrain[x, y] = cell;
    
            public bool IsEmpty
            {
                get
                {
                    for (var y = 0; y < Height; y++)
                        for (var x = 0; x < Width; x++)
                            if (_terrain[x, y] != null)
                                return false;
                    return true;
                }
            }
    
            public int CompareCells(MapCell c1, MapCell c2)
            {
                if (c1 == null)
                    throw new ArgumentNullException(nameof(c1));
                if (c2 == null)
                    throw new ArgumentNullException(nameof(c2));
                if (c1.F < c2.F)
                    return -1;
                if (c1.F > c2.F)
                    return 1;
                return 0;
            }
    
        }
    }

    Stigen beskrivs av klassen Path:

    using System.Collections.Generic;
    using System.Linq;
    
    namespace PathFinderAStar
    {
        public class Path : List<MapCell>
        {
            public Path()
            {
            }
    
            public Path(int count)
            {
                for (var i = 0; i < count; i++)
                    Add(null);
            }
    
            public MapCell GetCellAt(int x, int y) =>
                this.FirstOrDefault(c => c.X == x && c.Y == y);
        }
    }

    Både kartan (Map) och stigen (Path) består av referenser till celler som beskriver hur terrängen ser ut på en specifik plats. Typen heter MapCell (G, H, F och Index används av A*.):

    namespace PathFinderAStar
    {
        public class MapCell
        {
            private byte _walkCost;
            public int X { get; }
            public int Y { get; }
            public double G { get; set; }
            public double H { get; set; }
            public double F { get; set; }
            public int Index { get; set; }
    
            public byte WalkCost
            {
                get => _walkCost;
                set => _walkCost = value > 100 ? (byte)100 : value;
            }
    
            public bool IsWall =>
                WalkCost >= 100;
    
            public MapCell(int x, int y, byte walkCost)
            {
                X = x;
                Y = y;
                WalkCost = walkCost;
            }
        }
    }

    Sen har vi själva A*-algoritmen, såsom den implementerats av Christoph Husse, med mina förenklingar. Den konsumeras direkt i formulärets Load-event och ligger i klassen AStar.

    using System.Drawing;
    
    namespace PathFinderAStar
    {
        public class AStar
        {
            private Map Map { get; }
            private Map ClosedSet { get; }
            private Map OpenSet { get; }
            private Map CameFrom { get; }
            private Map RuntimeGrid { get; }
            private PriorityQueue OrderedOpenSet { get; }
    
            public AStar(Map map)
            {
                Map = map;
                ClosedSet = new Map(Map.Width, Map.Height);
                OpenSet = new Map(Map.Width, Map.Height);
                CameFrom = new Map(Map.Width, Map.Height);
                RuntimeGrid = new Map(Map.Width, Map.Height);
                OrderedOpenSet = new PriorityQueue(Map);
            }
    
            public Path FindPath(Point start, Point goal)
            {
                var startCell = Map.GetCell(start);
                var goalCell = Map.GetCell(goal);
                if (startCell == goalCell)
                    return new Path { startCell };
                ClosedSet.Clear();
                OpenSet.Clear();
                CameFrom.Clear();
                RuntimeGrid.Clear();
                OrderedOpenSet.Clear();
                startCell.G = 0.0;
                startCell.H = Map.GetDistance(startCell, goalCell);
                startCell.F = startCell.H;
                OpenSet.Add(startCell);
                OrderedOpenSet.Push(startCell);
                RuntimeGrid.Add(startCell);
                var neighbours = new Path(8);
                while (!OpenSet.IsEmpty)
                {
                    var next = OrderedOpenSet.Pop();
                    if (next == goalCell)
                    {
                        var result = ReconstructPath(
                            CameFrom,
                            CameFrom.GetCell(goalCell.X, goalCell.Y));
                        result.Add(goalCell);
                        return result;
                    }
                    OpenSet.Remove(next);
                    ClosedSet.Add(next);
                    StoreNeighbours(next, neighbours);
                    foreach (var currentNeighbour in neighbours)
                    {
                        if (currentNeighbour == null
                            || currentNeighbour.IsWall
                            || ClosedSet.Contains(currentNeighbour))
                            continue;
                        var tentativeG = RuntimeGrid.GetCell(next.X, next.Y).G
                            + Map.GetDistance(next, currentNeighbour);
                        if (!TentativeIsBetter(currentNeighbour,
                            tentativeG, out var added))
                            continue;
                        CameFrom.SetWithHistory(
                            currentNeighbour.X,
                            currentNeighbour.Y,
                           next);
                        if (!RuntimeGrid.Contains(currentNeighbour))
                            RuntimeGrid.Add(currentNeighbour);
                        currentNeighbour.G = tentativeG;
                        currentNeighbour.H = Map.GetDistance(
                            currentNeighbour,
                            goalCell);
                        currentNeighbour.F = currentNeighbour.G
                            + currentNeighbour.H;
                        if (added)
                            OrderedOpenSet.Push(currentNeighbour);
                        else
                            OrderedOpenSet.Update(currentNeighbour);
                    }
    
                }
                return null;
            }
    
            private bool TentativeIsBetter(
                MapCell currentNeighbour,
                double tentativeG,
                out bool added)
            {
                if (!OpenSet.Contains(currentNeighbour))
                {
                    OpenSet.Add(currentNeighbour);
                    added = true;
                    return true;
                }
                added = false;
                return tentativeG < RuntimeGrid.GetCell(
                    currentNeighbour.X,
                    currentNeighbour.Y).G;
            }
    
            private void StoreNeighbours(MapCell c, Path neighbours)
            {
                neighbours[0] = Map.GetCell(c.X - 1, c.Y - 1);
                neighbours[1] = Map.GetCell(c.X, c.Y - 1);
                neighbours[2] = Map.GetCell(c.X + 1, c.Y - 1);
                neighbours[3] = Map.GetCell(c.X - 1, c.Y);
                neighbours[4] = Map.GetCell(c.X + 1, c.Y);
                neighbours[5] = Map.GetCell(c.X - 1, c.Y + 1);
                neighbours[6] = Map.GetCell(c.X, c.Y + 1);
                neighbours[7] = Map.GetCell(c.X + 1, c.Y + 1);
            }
    
            private Path ReconstructPath(Map cameFrom, MapCell currentCell)
            {
                void ReconstructPathRecursive(
                    Map recursiveCameFrom,
                    MapCell recursiveCurrentCell,
                    Path result)
                {
                    var item = recursiveCameFrom.GetCell(
                        recursiveCurrentCell.X,
                        recursiveCurrentCell.Y);
                    if (item != null)
                        ReconstructPathRecursive(
                            recursiveCameFrom,
                            item,
                            result);
                    result.Add(recursiveCurrentCell);
    
                }
                var path = new Path();
                ReconstructPathRecursive(cameFrom, currentCell, path);
                return path;
            }
        }
    }

    Och slutligen, typen PriorityQueue som algoritmen använder för att hålla reda på vilka celler som undersöks, ser ut så här:

    using System.Collections.Generic;
    
    namespace PathFinderAStar
    {
        public class PriorityQueue
        {
            private List<MapCell> InnerList { get; } = new List<MapCell>();
            private Map Map { get; }
    
            public PriorityQueue(Map map)
            {
                Map = map;
            }
    
            public void Clear() =>
                InnerList.Clear();
    
            public int Push(MapCell c)
            {
                var index1 = InnerList.Count;
                c.Index = index1;
                InnerList.Add(c);
                do
                {
                    if (index1 <= 0)
                        break;
                    var index2 = (index1 - 1) / 2;
                    if (Compare(index1, index2) < 0)
                    {
                        SwitchElements(index1, index2);
                        index1 = index2;
                        continue;
                    }
                    break;
    
                } while (true);
                return index1;
            }
    
            public MapCell Pop()
            {
                var result = InnerList[0];
                InnerList[0] = InnerList[InnerList.Count - 1];
                InnerList[0].Index = 0;
                InnerList.RemoveAt(InnerList.Count - 1);
                result.Index = -1;
                var p = 0;
                do
                {
                    var pn = p;
                    var p1 = 2 * p + 1;
                    var p2 = 2 * p + 2;
                    if (InnerList.Count > p1 && Compare(p, p1) > 0)
                        p = p1;
                    if (InnerList.Count > p2 && Compare(p, p2) > 0)
                        p = p2;
                    if (p == pn)
                        break;
                    SwitchElements(p, pn);
                } while (true);
                return result;
            }
    
            public void Update(MapCell cell)
            {
                var count = InnerList.Count;
                while (cell.Index - 1 > 0
                    && Compare(cell.Index - 1, cell.Index) > 0)
                    SwitchElements(cell.Index - 1, cell.Index);
                while (cell.Index + 1 < count
                    && Compare(cell.Index + 1, cell.Index) < 0)
                    SwitchElements(cell.Index + 1, cell.Index);
            }
    
            private int Compare(int i1, int i2) =>
                Map.CompareCells(InnerList[i1], InnerList[i2]);
    
            private void SwitchElements(int i1, int i2)
            {
                var c1 = InnerList[i1];
                InnerList[i1] = InnerList[i2];
                InnerList[i2] = c1;
                InnerList[i1].Index = i1;
                InnerList[i2].Index = i2;
            }
        }
    }

    Med denna kod på plats har man en enkel funktion (FindPath) att anropa för att hitta en stig mellan två punkter (om sådan finns). Och om man vill anpassa kriterierna för hur en stig väljs, t.ex. hur terrängen ska viktas mot avståndet, görs det i funktionen GetDistance i klassen Map.

  • Diamantproblemet i C# 8

    Diamantproblemet i C# 8

    Idag skriver jag på Nethouse-bloggen om diamantproblemet i C#, som introduceras med default interface members. Läs gärna inlägget här.

  • IMDb Scraper

    IMDb Scraper

    IMDb Scraper is a simple library for extracting a movie title and year from a IMDb ID.

    Installation (.NET Framework 4.6):

    Install-Package ImdbScraper

    Example:

    var repository = new Repository();
    var result = repository.GetMovie(87332);
    Console.WriteLine(result.ToString());

     

    Update 2023-06-18: The scraper now requires .NET 7.0, and it also downloads the movie rating from imdb.com.

  • Entity Framework 6

    Entity Framework 6

    Entity Framework 6 (EF6) är mycket enkelt att komma igång med. För att få tillgång till aktuell version, använd Nuget-paketet EntityFramework. Den här bloggposten visar det minsta man behöver veta för att komma igång med EF6 i sitt program.

    Install-Package EntityFramework

    Dessutom behöver du ha en tom databas att jobba mot. I detta exempel antar jag att databasen heter EfTest. Anslutningssträngen lagras i konfigurationsfilen. I mitt fall är det en vanlig Console Application.

      <connectionStrings>
        <add name="EfTest" providerName="System.Data.SqlClient" connectionString="Data Source=XXX;Initial Catalog=EfTest;Integrated Security=True" />
      </connectionStrings>

    Nästa steg är att skapa DbContext-objektet. Denna ärver från System.Data.Entity.DbContext. Basklassen behöver veta vilken anslutningssträng som ska användas. Antingen skickar man in själva anslutningssträngen, eller så hänvisar man till ett namn. De tabeller som ska användas beskrivs av klasser och väljs in som argument till den generiska DbSet (också i System.Data.Entity). Här har jag helt enkelt skapat två klasser som heter Tabell1 och Tabell2.

        public class TestContext : DbContext
        {
            public TestContext() : base("name=EfTest")
            {
            }
            public DbSet<Tabell1> Tabell1 { get; set; }
            public DbSet<Tabell2> Tabell2 { get; set; }
        }

    Låt oss titta på klassen Tabell1. Den innehåller en primärnyckel (int) och en sträng som heter Value. Attributet Key på fältet (System.ComponentModel.DataAnnotations) anger att motsvarande kolumn i databasen är en primärnyckel. Attributet DatabaseGenerated (System.ComponentModel.DataAnnotations.Schema) använder jag här för att säga att kolumnen ska vara en räknare, och alltså inte något som jag ska tillhandahålla data till vid tillägg. Det innebär alltså att nya poster skapas genom att Value får ett värde, inte Tabell1Id.

        using (var db = new TestContext())
        {
            db.Tabell1.Add(new Tabell1 {Value = "Hej"});
            db.SaveChanges();
        }

    När tabellen läses av, kan man konstatera att nyckelkolumnen i detta fall ska ha fått sitt värde ändå.

        using (var db = new TestContext())
        {
            foreach (var tabell1 in db.Tabell1)
            {
                Console.WriteLine($"{tabell1.Tabell1Id} - {tabell1.Value}");
            }
        }

    Det är när programmet körs första gången som databasen byggs upp automatiskt. Om en befintlig tabell förändras, t.ex. genom att klassen Tabell1 får en till property (vilket motsvarar en till kolumn) kommer programmet inte längre att fungera. Då måste en strategi för uppgradering tillhandahållas. T.ex. kan man säga att databasschemat helt enkelt ska genereras om vid förändring. Det innebär att man förlorar allt data, så med denna strategi vill man förmodligen komplettera med kod som lägger in lite testdata.

    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TestContext>());

    För att tillhandahålla testdata, skriv en egen initializer, baserad på den som du vill använda, och tillhandahåll testdata i funktionen Seed.

        public class DropCreateInitializer : DropCreateDatabaseIfModelChanges<TestContext>
        {
            protected override void Seed(TestContext context)
            {
                context.Tabell1.Add(new Tabell1 { Value = "Hej" });
                context.Tabell1.Add(new Tabell1 { Value = "Oj" });
            }
        }

    Sen är det bara att använda sin egna initializer.

    Database.SetInitializer(new DropCreateInitializer());
  • Fullskärm med exakt virtuell bildupplösning

    Fullskärm med exakt virtuell bildupplösning

    Om du vill bygga ett spel med Monogame som 1) körs i fullsärmsläge och 2) har en låg virtuell upplösning, typ 320 x 200 pixlar, så finns en enkel lösning. Istället för att rendera spelet på din backbuffer, så renderar du till en render target som är just 320 x 200 pixlar stor. För att åstadkomma detta, skapa en ny property i ditt spel, av typen RenderTarget2D.

    private RenderTarget2D renderTarget { get; set; }

    I konstruktorn, givet att vi befinner oss i release-kompileringen, skapar man en backbuffer som är lika stor som skärmen.

                graphics = new GraphicsDeviceManager(this);
    #if DEBUG
                ScreenWidth = 640;
                ScreenHeight = 400;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = false;
    #else
                ScreenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                ScreenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = true;
    #endif
                Content.RootDirectory = "Content";

    Funktionen Initialize är en lämplig plats att skapa RenderTarget2D-objektet. Det är storleken på den som styr den virtuella upplösningen.

    renderTarget = new RenderTarget2D(GraphicsDevice, 320, 200);

    Slutligen renderas spelet mot RenderTarget2D-objektet. Funktionen Draw skulle kunna se ut så här:

    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.SetRenderTarget(renderTarget);
        graphics.GraphicsDevice.Clear(Color.Black);
    
        //Rita spelet här!
        spriteBatch.Begin();
        spriteBatch.Draw(hej, new Vector2(0, 0), Color.White);
        spriteBatch.Draw(hej, new Vector2(310, 190), Color.White);
        spriteBatch.End();
    
        graphics.GraphicsDevice.SetRenderTarget(null);
        spriteBatch.Begin(samplerState: SamplerState.PointClamp);
        spriteBatch.Draw(renderTarget, new Rectangle(0, 0, ScreenWidth, ScreenHeight), new Rectangle(0, 0, 320, 200), Color.White);
        spriteBatch.End();
        base.Draw(gameTime);
    }

    Notera det sista anropet på spriteBatch.Draw, den tredje sista raden. Den skalar upp spelet till den riktiga back bufferten. Om man vill ha rektangulära pixlar, ska det hanteras där. Denna exempelkod tar inte hänsyn till olika bildförhållanden.

    Så här ser hela koden ut:

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    
    namespace Game1
    {
        public class Game1 : Game
        {
            private RenderTarget2D renderTarget { get; set; }
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            private int ScreenWidth { get; }
            private int ScreenHeight { get; }
            private Texture2D hej;
    
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
    #if DEBUG
                ScreenWidth = 640;
                ScreenHeight = 400;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = false;
    #else
                ScreenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                ScreenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                graphics.PreferredBackBufferWidth = ScreenWidth;
                graphics.PreferredBackBufferHeight = ScreenHeight;
                graphics.IsFullScreen = true;
    #endif
                Content.RootDirectory = "Content";
            }
            protected override void Initialize()
            {
                renderTarget = new RenderTarget2D(GraphicsDevice, 320, 200);
                base.Initialize();
            }
            protected override void LoadContent()
            {
                spriteBatch = new SpriteBatch(GraphicsDevice);
                hej = Content.Load<Texture2D>("hej");
            }
            protected override void Update(GameTime gameTime)
            {
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                    Exit();
                base.Update(gameTime);
            }
            protected override void Draw(GameTime gameTime)
            {
                graphics.GraphicsDevice.SetRenderTarget(renderTarget);
                graphics.GraphicsDevice.Clear(Color.Black);
    
                //Rita spelet här!
                spriteBatch.Begin();
                spriteBatch.Draw(hej, new Vector2(0, 0), Color.White);
                spriteBatch.Draw(hej, new Vector2(310, 190), Color.White);
                spriteBatch.End();
    
                graphics.GraphicsDevice.SetRenderTarget(null);
                spriteBatch.Begin(samplerState: SamplerState.PointClamp);
                spriteBatch.Draw(renderTarget, new Rectangle(0, 0, ScreenWidth, ScreenHeight), new Rectangle(0, 0, 320, 200), Color.White);
                spriteBatch.End();
                base.Draw(gameTime);
            }
        }
    }
    

    Jag utvecklar detta på min arbetsgivares blogg.

  • Skala upp Monogame

    Skala upp Monogame

    När man bygger retrospel med en upplösning på omkring 320 x 200 pixlar, vill man gärna skala upp spelet så att sprites och andra objekt inte blir så små på en modern skärm. För den som programmerar Monogame finns flera lösningar. Jag tycker att den enklaste metoden är att använda ett transform matrix. När jag debuggar mitt spel, tycker jag att en uppskalning på 3 x 3 är rätt lämplig, vilket innebär att ett fönster på 960 x 600 pixlar är lämpligt. Detta är spelets konstruktor, spelet har kompletterats med en medlemsvariabel vid namn transformMatrix av typen Matrix.

    graphics.PreferredBackBufferWidth = 960;
    graphics.PreferredBackBufferHeight = 600;
    graphics.IsFullScreen = false;
    transformMatrix = Matrix.CreateScale(960 / 320, 600 / 200, 1.0f);

    I skarpt läge låter jag skärmupplösningen styra skalningen, eftersom jag vill att spelet ska köras i fullskärm.

    var width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
    var height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
    graphics.PreferredBackBufferWidth = width;
    graphics.PreferredBackBufferHeight = height;
    graphics.IsFullScreen = true;
    transformMatrix = Matrix.CreateScale(width / 320, height / 200, 1.0f);
    

    För att effekten ska slå igenom, ska Matrix-objektet skickas med till SpriteBatch-objektets Begin-metod. För att undvika oönskad kantutjämning använder jag en sampler state som heter PointClamp.

    spriteBatch.Begin(transformMatrix: transformMatrix, samplerState: SamplerState.PointClamp);

    Ett exempelspel skulle kunna se ut så här:

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    
    namespace Game1
    {
        public class Game1 : Game
        {
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            Matrix transformMatrix;
            Texture2D testtexture { get; set; }
            private int x = 0;
            private int y = 0;
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
    #if DEBUG
                graphics.PreferredBackBufferWidth = 960;
                graphics.PreferredBackBufferHeight = 600;
                graphics.IsFullScreen = false;
                transformMatrix = Matrix.CreateScale(960 / 320, 600 / 200, 1.0f);
    #else
                var width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                var height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                graphics.PreferredBackBufferWidth = width;
                graphics.PreferredBackBufferHeight = height;
                graphics.IsFullScreen = true;
                transformMatrix = Matrix.CreateScale(width / 320, height / 200, 1.0f);
    #endif
                Content.RootDirectory = "Content";
            }
            protected override void LoadContent()
            {
                spriteBatch = new SpriteBatch(GraphicsDevice);
                testtexture = Content.Load<Texture2D>("my_folder");
            }
            protected override void Update(GameTime gameTime)
            {
                var state = Keyboard.GetState();
                if (state.IsKeyDown(Keys.Escape))
                    Exit();
                if (state.IsKeyDown(Keys.Left))
                    x--;
                else if (state.IsKeyDown(Keys.Right))
                    x++;
                if (state.IsKeyDown(Keys.Up))
                    y--;
                else if (state.IsKeyDown(Keys.Down))
                    y++;
                base.Update(gameTime);
            }
            protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
                spriteBatch.Begin(transformMatrix: transformMatrix, samplerState: SamplerState.PointClamp);
                spriteBatch.Draw(testtexture, new Vector2(x, y), Color.White);
                spriteBatch.End();
                base.Draw(gameTime);
            }
        }
    }
    

    Ett problem som man bör känna till, är att om bildförhållandet mellan den virtuella upplösningen och den fysiska upplösningen inte är samma, så kommer fler pixlar än önskat att synas. När man kör i fönster, så kan man styra detta så att bildförhållandet är detsamma. 960 x 600 har samma bildförhållande som 320 x 200. Men i fullskärmsläge bör man ha detta i åtanke.

    Jag utvecklar detta på min arbetsgivares blogg.

  • Labyrint (recursive backtracking)

    Labyrint (recursive backtracking)

    Jag har börjat arbeta med ett datorspel i kategorin roguelike. Varje spel ska vara unikt, så kartan genereras när en användare spelar spelet. Vid uppstart skapar jag en labyrint som sen används som kontur för rummen som genereras efter behov. De rum som spelaren inte besöker behöver man trots allt inte hålla i RAM, men jag vill ha konturen färdig för att kunna ha lite kontroll över kartans kvalité.

     
    Totalt okunnig om labyrintalgoritmer läste jag igenom Wikipedias artikel om recursive backtracking som i korthet går ut på följande:
    1. Utgå från den cell som ska vara startpunkten, C.
     
    2. Om C inte är vald sedan tidigare, plocka bort en slumpvis utvald vägg på C. Följ den nyskapade öppningen och låt den vara C.
     
    3. Om nya C har varit C tidigare, stega bakåt till första cell som fortfarande har fyra väggar, gör den till C, upprepa steg 2.
     
    4. Om C nu är samma som var C vid punkt 1 så är labyrinten klar.
     
    Så i princip startar mitt (blivande) datorspel med en hel hög av celler (precis hur många jag vill) som håller information om in- och utgångar (konturen), men det är först när cellen används som fyller jag på med s.k. tiles, alltså de byggstenar som bestämmer kartans detaljer. Låt oss titta på själva labyrinten, och lämna resten därhän.
    För detta exempel valde jag språket C#. Trots att språket är lite pratigt, är det ganska flexibelt och användbart.

    Först av allt behövs en entitet – en cell – som kan ha väggar i fyra riktningar: Norr, öst, söder och väst. Jag anger att jag både vill kunna läsa (get) och skriva (set) dessa egenskaper under programmets gång. Initialt måste cellen ha alla sina fyra möjliga väggar.

    public class Cell
    {
        public bool WallNorth { get; set; } = true;
        public bool WallEast { get; set; } = true;
        public bool WallSouth { get; set; } = true;
        public bool WallWest { get; set; } = true;
    }

    Vidare måste cellen kunna svara på var i labyrinten den befinner sig. Det behövs för att cellens grannar ska kunna hittas. En cell som inte kan svara på det, får inte skapas över huvudet taget.

        public int X { get; }
        public int Y { get; }
        public Cell(int x, int y)
        {
            X = x;
            Y = y;
        }

    Eftersom cellen enligt algoritmens regler måste kunna svara på hur många väggar den har, måste vi beskriva hur vi kan veta det.

        public int WallsCount
        {
            get
            {
                var c = WallNorth ? 1 : 0;
                c += WallEast ? 1 : 0;
                c += WallSouth ? 1 : 0;
                c += WallWest ? 1 : 0;
                return c;
            }
        }

    Och slutligen måste cellen kunna berätta om sin position i ett lämpligt format.

        public Point Position => new Point(X, Y);

    Därefter behövs en representation av själva labyrinten som kan hålla en matris av celler. Denna gång väljer jag att skapa en liten labyrint med 100 celler (10 x 10). Bredden och höjden måste kunna läsas (get) under programmets gång. Koden skapar inte cellerna, endast platshållaren för dem.

    public class Labyrinth
    {
        public static int Width { get; } = 10;
        public static int Height { get; } = 10;
        public Cell[,] Cell = new Cell[Width, Height];
    }

    Sen ska vi beskriva algoritmen i labyrintens konstruktor (=funktion med ansvar för initiering). Vi börjar med att berätta att vi vill kunna skapa slumptal, och att platshållaren för våra celler verkligen ska innehålla celler.

            var rnd = new Random();
            for (var y = 0; y < Height; y++)
                for (var x = 0; x < Width; x++)
                    Cells[x, y] = new Cell(x, y);

    Därefter måste vi definiera konceptet med grannar för konstruktorn. Grannar är angränsande celler.

            Func<int, int, List<Point>> getNeighbours = (x, y) =>
            {
                var ret = new List<Point>();
                if (y > 0 && Cells[x, y - 1].WallsCount >= 4)
                    ret.Add(Cells[x, y - 1].Position);
                if (x < Width - 1 && Cells[x + 1, y].WallsCount >= 4)
                    ret.Add(Cells[x + 1, y].Position);
                if (y < Height - 1 && Cells[x, y + 1].WallsCount >= 4)
                    ret.Add(Cells[x, y + 1].Position);
                if (x > 0 && Cells[x - 1, y].WallsCount >= 4)
                    ret.Add(Cells[x - 1, y].Position);
                return ret;
            };

    Sen måste vi beskriva hur man slår in en vägg. Det är inte så enkelt som att sätta en cells vägg-egenskap (t.ex. WallNorth) till false, eftersom grannen åt det hållet också måste få sin motsvarande vägg (t.ex. WallSouth) satt till false.

            Action<Cell, Point> knockWall = (cell1, cell2) =>
            {
                if (cell2.Y < cell1.Y)
                {
                    cell1.WallNorth = false;
                    Cells[cell2.X, cell2.Y].WallSouth = false;
                    return;
                }
                if (cell2.X > cell1.X)
                {
                    cell1.WallEast = false;
                    Cells[cell2.X, cell2.Y].WallWest = false;
                    return;
                }
                if (cell2.Y > cell1.Y)
                {
                    cell1.WallSouth = false;
                    Cells[cell2.X, cell2.Y].WallNorth = false;
                    return;
                }
                if (cell2.X < cell1.X)
                {
                    cell1.WallWest = false;
                    Cells[cell2.X, cell2.Y].WallEast = false;
                    return;
                }
            };

    Nu har konstruktorn tillgång till resurserna, och den vet vad en granne är och hur man slår in en vägg. Dags att sätta igång! Vi behöver hålla koll på vilka celler vi besökt…

                var queue = new Queue<Cell>();

    …och så behöver vi en slumpvis utvald cell att börja i.

                var c = Cells[rnd.Next(Width), rnd.Next(Height)];
                queue.Enqueue(c);

    Därefter, slå ut en vägg och följ efter. Om det inte går, backa till start och prova igen.

                while (queue.Count > 0)
                {
                    var neighbours = getNeighbours(c.X, c.Y);
                    if (neighbours.Count > 0)
                    {
                        var neighbour = neighbours[rnd.Next(neighbours.Count)];
                        knockWall(c, neighbour);
                        queue.Enqueue(c);
                        c = Cells[neighbour.X, neighbour.Y];
                    }
                    else
                    {
                        c = queue.Dequeue();
                    }
                }

     
    För att representera detta på skärmen, behöver jag bara bekymra mig om två av väggarna, höger och under. Högra grannens vänstervägg och undre grannens övre vägg har samma status (tack vare knockWall).
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            const int size = 20;
            for (var y = 0; y < Labyrinth.Height; y++)
                for (var x = 0; x < Labyrinth.Width; x++)
                {
                    if (Labyrinth.Cells[x, y].WallEast)
                        e.Graphics.DrawLine(Pens.Black, x * size + (size - 1), y * size, x * size + (size - 1), y * size + (size - 1));
                    if (Labyrinth.Cells[x, y].WallSouth)
                        e.Graphics.DrawLine(Pens.Black, x * size, y * size + (size - 1), x * size + (size - 1), y * size + (size - 1));
                }
        }
     
    Hela källkoden ser ut så här:

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            private Labyrinth Labyrinth { get; } = new Labyrinth();
            public Form1()
            {
                InitializeComponent();
            }
    
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            const int size = 20;
            for (var y = 0; y < Labyrinth.Height; y++)
                for (var x = 0; x < Labyrinth.Width; x++)
                {
                    if (Labyrinth.Cells[x, y].WallEast)
                        e.Graphics.DrawLine(Pens.Black, x * size + (size - 1), y * size, x * size + (size - 1), y * size + (size - 1));
                    if (Labyrinth.Cells[x, y].WallSouth)
                        e.Graphics.DrawLine(Pens.Black, x * size, y * size + (size - 1), x * size + (size - 1), y * size + (size - 1));
                }
        }
        }
        public class Cell
        {
            public bool WallNorth { get; set; } = true;
            public bool WallEast { get; set; } = true;
            public bool WallSouth { get; set; } = true;
            public bool WallWest { get; set; } = true;
            public int X { get; }
            public int Y { get; }
            public Cell(int x, int y)
            {
                X = x;
                Y = y;
            }
            public int WallsCount
            {
                get
                {
                    var c = WallNorth ? 1 : 0;
                    c += WallEast ? 1 : 0;
                    c += WallSouth ? 1 : 0;
                    c += WallWest ? 1 : 0;
                    return c;
                }
            }
            public Point Position => new Point(X, Y);
        }
        public class Labyrinth
        {
            public static int Width { get; } = 10;
            public static int Height { get; } = 10;
            public Cell[,] Cells = new Cell[Width, Height];
            public Labyrinth()
            {
                var rnd = new Random();
                for (var y = 0; y < Height; y++)
                    for (var x = 0; x < Width; x++)
                        Cells[x, y] = new Cell(x, y);
                Func<int, int, List<Point>> getNeighbours = (x, y) =>
                {
                    var ret = new List<Point>();
                    if (y > 0 && Cells[x, y - 1].WallsCount >= 4)
                        ret.Add(Cells[x, y - 1].Position);
                    if (x < Width - 1 && Cells[x + 1, y].WallsCount >= 4)
                        ret.Add(Cells[x + 1, y].Position);
                    if (y < Height - 1 && Cells[x, y + 1].WallsCount >= 4)
                        ret.Add(Cells[x, y + 1].Position);
                    if (x > 0 && Cells[x - 1, y].WallsCount >= 4)
                        ret.Add(Cells[x - 1, y].Position);
                    return ret;
                };
                Action<Cell, Point> knockWall = (cell1, cell2) =>
                {
                    if (cell2.Y < cell1.Y)
                    {
                        cell1.WallNorth = false;
                        Cells[cell2.X, cell2.Y].WallSouth = false;
                        return;
                    }
                    if (cell2.X > cell1.X)
                    {
                        cell1.WallEast = false;
                        Cells[cell2.X, cell2.Y].WallWest = false;
                        return;
                    }
                    if (cell2.Y > cell1.Y)
                    {
                        cell1.WallSouth = false;
                        Cells[cell2.X, cell2.Y].WallNorth = false;
                        return;
                    }
                    if (cell2.X < cell1.X)
                    {
                        cell1.WallWest = false;
                        Cells[cell2.X, cell2.Y].WallEast = false;
                        return;
                    }
                };
                var queue = new Queue<Cell>();
                var c = Cells[rnd.Next(Width), rnd.Next(Height)];
                queue.Enqueue(c);
                while (queue.Count > 0)
                {
                    var neighbours = getNeighbours(c.X, c.Y);
                    if (neighbours.Count > 0)
                    {
                        var neighbour = neighbours[rnd.Next(neighbours.Count)];
                        knockWall(c, neighbour);
                        queue.Enqueue(c);
                        c = Cells[neighbour.X, neighbour.Y];
                    }
                    else
                    {
                        c = queue.Dequeue();
                    }
                }
            }
        }
    }
    
    Och detta är resultatet:
     
  • Lite nyheter i C# 7: Lokala funktioner

    Lite nyheter i C# 7: Lokala funktioner

    Till en viss grad har man kunnat skapa lokala funktioner i C# under en tid.

    public void DoSomething()
    {
        Func<int, int> d = (x) => x * 2;
        for (var i = 1; i <= 8; i++)
            Console.WriteLine(d(i));
    }

    I C# 7 kan man skapa fullvärdiga funktioner i en annan funktion. Det enda som egentligen skiljer en lokal funktion från en medlemsfunktion (eller en static) är att lokala funktioner inte har någon angiven synlighet, eftersom det inte är relevant.

    public void DoSomething()
    {
        int d(int x) => x * 2;
        for (var i = 1; i <= 8; i++)
            Console.WriteLine(d(i));
    }

    Fördelen med riktiga lokala funktioner är att det ger tillgång till parameterarrayer, referensparametrar, utparametrar och generics, alltså sådant som inte kunde användas i gamla inline-funktioner.

    Closure-hanteringen är lite intressant. Följande exempel använder bara gammal C# och kompilerar inte, eftersom variabeln MinFunktion som håller inlinefunktionen inte finns än.

    public void DoSomething()
    {
        MinFunktion();
        int x = 10;
        Action MinFunktion = () => x++;
        Console.WriteLine(x);
    }

    Om vi istället flyttar ner anropet, skrivs 11 ut på skärmen, eftersom x ökas med 1 i MinFunktion.

    public void DoSomething()
    {
        int x = 10;
        Action MinFunktion = () => x++;
        MinFunktion();
        Console.WriteLine(x);
    }

    Resultatet blir detsamma om jag ersätter MinFunktion med en fullvärdig lokal funktion. Det x som ökas i MinFunktion är en closure.

    public void DoSomething()
    {
        int x = 10;
        void MinFunktion() => x++;
        MinFunktion();
        Console.WriteLine(x);
    }

    Men detta är riktigt intressant. Eftersom MinFunktion nu är en fullvärdig funktion, så skapas den i den yttre funktionen DoSomething vid kompilering. Så nu kan jag anropa den innan variabeln x finns.

    public void DoSomething()
    {
        MinFunktion();
        int x = 10;
        void MinFunktion() => x++;
        Console.WriteLine(x);
    }

    Svaret blir då 10, eftersom det inte är closure-variabeln x som blir ökad, utan en x som är lokal för MinFunktion.

    Slutligen, eftersom lokala funktioner är fullvärdiga funktioner, kan de innehålla egna lokala funktioner. Snyggt!

    Så vad fungerar inte?

    Generellt sett kan en värdetyp inte innehålla null, men en referenstyp kan innehålla null. Detta är vettigt, eftersom en referenstyp innehåller just en referens som kan vara eller inte vara satt.

    Vi har haft nullable types i C# en tid nu. Det är en struktur som ger värdetyper en null-flagga, så att dessa kan vara null.

    public int X { get; set; } //Ej initierad - innehåller 0.
    public int? Y { get; set; } //Ej initierad - innehåller null.
    

    Snart ska man kunna förhindra referenstyper från att vara null, vilket är praktiskt t.ex. om man vill att en parameter ska vara obligatorisk. Troligen kommer det att se ut ungefär så här:

    public MyType A { get; set; } //Kan innehålla null
    public MyType! B { get; set; } = new MyType(); //Kan inte innehålla null
    

    C# 7 ska ha visst stöd för pattern matching, men det fungerar inte (än).

    Inte heller record types fungerar. Record types är klasser som endast innehåller properties, som deklareras med en enda rad kod.

    Det finns alltså anledning att titta med på C# 7 längre fram.

  • Lite nyheter i C# 7: Tuples

    Lite nyheter i C# 7: Tuples

    Om jag vill skapa en funktion som skickar tillbaka två värden, kan det lösas med en tuple. I .NET Framework är tuples implementerat som en generisk klass. Denna funktion skickar tillbaka en int och en string:

    public static Tuple<int, string> DoSomething()
    {
        return Tuple.Create(10, "Hello");
    }

    Ett anrop som tar emot svaren skulle kunna se ut så här. I detta fall är response av typen Tuple<int, string>, vilket innebär att Item1 är en int och Item2 en string.

    var response = DoSomething();
    Console.WriteLine(response.Item1); //10
    Console.WriteLine(response.Item2); //"Hello"

    Eftersom C# 7 har inbyggt stöd för tuples, behöver jag inte hänvisa till klassen Tuple. Det går helt enkelt att deklarera fler returer på en funktion.

    Samma funktion i C# 7 kan se ut så här (givet att NuGet-paketet System.ValueTuple är på plats):

    public static (int, string) DoSomething()
    {
        return (10, "Hello");
    }

    Koden som utför anropet kan se lika dan ut. Variabeln response är nu en (int, string) istället för en Tuple<int, string>.

    Funktionens returvärden kan dessutom ha specificerade namn, vilket gör koden mer självdokumenterande.

    public static (int i, string s) DoSomething()
    {
        return (10, "Hello");
    }

    Anropet:

    var response = DoSomething();
    Console.WriteLine(response.i); //10
    Console.WriteLine(response.s); //"Hello"

    Tuples kan skapas inline (x nedan), inline med namngivna medlemmar (y nedan) eller inline med type inference (z nedan).

    (int, int, int) x = (10, 20, 30);
    Console.WriteLine(x.Item1); //10
    
    (int kalle, int pelle, int sven) y = (10, 20, 30);
    Console.WriteLine(y.kalle); //10
    
    var z = (10, 20, 30);
    Console.WriteLine(z.Item1); //10
  • .NET Sprite

    .NET Sprite

    Hårdvarurenderade sprites i C#:

    Github: https://github.com/r1sc/Sprite
    Nuget: https://www.nuget.org/packages/Sprite/

    …och så några “bloopers“…

  • List invocations in constructor

    List invocations in constructor

    This example shows how to list the method invocations in a C# file. This is the file that will be read:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication1
    {
      public class MyClass
      {
        public MyClass()
        {
          Console.WriteLine("Hello.");
          DoSomething();
          DoSomethingElse();
        }
    
        private void DoSomething()
        {
        }
    
        private void DoSomethingElse()
        {
        }
      }
    }

    To install CodeSearchTree in your Visual Studio project, type Install-Package CodeSearchTree in the package manager console. Your project must use .NET Framework 4.6 or higher.

    The path to the first method invocation is ns/cls/constructor/block/expression/invocation, but since each invocation is located under an expression node, we will iterate through those. The code will list the three calls made in the constructor of the example class above.

    //Load the C# class file.
    var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");
    
    //Get the first expression in the constructor.
    var exp = tree.GetChild("ns/cls/constructor/block/expression");
    
    while (exp != null)
    {
      //Get the invocation and type out the source code.
      var invocation = exp.GetChild("invocation");
      if (invocation != null)
        Console.WriteLine(invocation.Source);
    
      //Get the next node. Note that the next node might be of any kind.
      exp = exp.GetNextSibling();
    }

    If you like to use the typed version of the CodeSearchTree API, your code should look like this:

    //Load the C# class file.
    var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");
    
    //Get the first expression in the constructor.
    var exp = tree.Ns.Cls.Constructor.Block?.Expression?.SearchResult;
    
    while (exp != null)
    {
      //Get the invocation and type out the source code.
      var invocation = exp.Invocation?.SearchResult;
      if (invocation != null)
        Console.WriteLine(invocation.Source);
    
      //Get the next node. Note that the next node might be of any kind.
      exp = exp.GetNextSibling();
    }

    The output in both cases will be the source code for the three invocations. But let’s say that you want the function names only. The names will be found in different places for different invocations. The first invocation a memberaccess node and two id nodes. The first is called Console and the second is the name of the function. The two other invocations consists of only one id node. We must look at two places. This code will list the three function names only.

    //Load the C# class file.
    var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");
    
    //Get the first expression in the constructor.
    var exp = tree.GetChild("ns/cls/constructor/block/expression");
    
    while (exp != null)
    {
      //Get the invocation and type out the source code.
      var invocation = exp.GetChild("invocation");
      if (invocation != null)
      {
        var id = invocation.GetChild("memberaccess/id[1]");
        if (id != null)
          Console.WriteLine(id.Name);
        else
        {
          id = invocation.GetChild("id");
          if (id != null)
            Console.WriteLine(id.Name);
        }
      }
      //Get the next node. Note that the next node might be of any kind.
      exp = exp.GetNextSibling();
    }

    Again, if you like to use the typed version of the CodeSearchTree API, your code should look like this:

    //Load the C# class file.
    var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");
    
    //Get the first expression in the constructor.
    var exp = tree.Ns.Cls.Constructor.Block?.Expression?.SearchResult;
    
    while (exp != null)
    {
      //Get the invocation and type out the source code.
      var invocation = exp.Invocation?.SearchResult;
      if (invocation != null)
      {
        //No need for SearchResult property since the indexer gives the search result.
        var id = invocation?.MemberAccess?.Id[1];
        if (id != null)
          Console.WriteLine(id.Name);
        else
        {
          id = invocation?.Id?.SearchResult;
          if (id != null)
            Console.WriteLine(id.Name);
        }
      }
    
      //Get the next node. Note that the next node might be of any kind.
      exp = exp.GetNextSibling();
    }

    Output:

    WriteLine
    DoSomething
    DoSomethingElse

    Project website: https://github.com/Anders-H/CodeSearchTree

  • Parse Transact SQL

    Parse Transact SQL

    To parse T-SQL from C#, add a reference to the following libraries (listed under Extensions, not under Framework):

    Microsoft.Data.Schema.ScriptDom
    Microsoft.Data.Schema.ScriptDom.Sql

    My examples imports the following namespaces:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Data.Schema.ScriptDom;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using System.IO;

    This code shows how to parse a boolean expression:

    //Parse
    var p = new TSql100Parser(false);
    IList<ParseError> err;
    var sql = "(a > 10) and (i < 10)";
    var exp = p.ParseBooleanExpression(new StringReader(sql), out err);
    
    //Errors? Empty if none.
    Console.WriteLine($"{err.Count} errors.");
    err.ToList().ForEach(x =>
       Console.WriteLine(
          $"Error \"{x.Message}\" at line {x.Line}."));
    
    //Result. Null if errors.
    if (!(exp == null))
       exp.ScriptTokenStream.ToList().ForEach(x =>
          Console.WriteLine(
             $"Token type: {x.TokenType}, source: {x.Text}"));

    If you want to parse a complete Transact SQL program, call the ParseStatementList function instead of the ParseBooleanExpression function.

    //Parse
    var p = new TSql100Parser(false);
    IList<ParseError> err;
    var sql = "SELECT EmployeeID, FirstName FROM dbo.Employees WHERE FirstName LIKE 'N%'";
    var exp = p.ParseStatementList(new StringReader(sql), out err);
    
    Console.WriteLine($"{err.Count} errors.");
    err.ToList().ForEach(x =>
       Console.WriteLine(
          $"Error \"{x.Message}\" at line {x.Line}."));
    
    //Result. Null if errors.
    if (!(exp == null))
       exp.ScriptTokenStream.ToList().ForEach(x =>
          Console.WriteLine(
             $"Token type: {x.TokenType}, source: {x.Text}"));

    This is the expected output:

    sqlparse

  • CodeSearchTree use case: Read constant values

    CodeSearchTree use case: Read constant values

    For this example, I am using Visual Studio 2015 Community edition to create a console application.  Make sure that you are using .NET Framework 4.6 or later. Let’s say that you have a .cs file with constants that looks something like this:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MySystem
    {
      public class Settings
      {
        public const int Iterations = 56;
        public const string TargetFilename = @"C:\Output\log.txt";
        public const int Length = 128;
        public const bool Repeat = true;
      }
    }

    You want to know the names and values in this file. Let’s start from the beginning. In my console application, I want a reference to the CodeSearchTree – I accept your complements for my choice of name. Just to be on the safe side, I specify the name of the project that use the CodeSearchTree library from when I add the package in the Package Manager Console.

    Get-Project MyProject | Install-Package CodeSearchTree

    In the Main function, first declare the filename variable, then:

    //Load and parse the source file.
    var tree = CodeSearchTree.Node.CreateTreeFromFile(filename);
    //Get the first constant.
    var f = tree.GetChild("ns/cls/field");
    //Display name and value and get the next constant.
    while (!(f == null))
    {
      if (f.NodeType == CodeSearchTree.NodeType.FieldDeclarationSyntaxNode)
      {
        //Display...
        var n = f.Name;
        var x = "vardeclaration/vardeclarator/equalsvalue/literal";
        var v = f.GetChild(x).Source;
        Console.WriteLine($"Name: {f.Name}, Value: {v}");
      }
      //Get next...
      f = f.GetNextSibling();
    }

    If you are unsure about the path strings in your C# tree, try to drop your file on the test client. Happy searching!

  • Lathund XAML: Databinding

    Lathund XAML: Databinding

    Kontrollen ListView kan användas för att presentera data i listform, och tillsammans med GridView kan man använda den för att presentera tabulär data i listform. Följande exempel fyller ett fönster med en ListView, som har en GridView med tre kolumner.

    <ListView x:Name="listView" Margin="5,5,5,5">
       <ListView.View>
          <GridView>
             <GridViewColumn Header="First name" Width="100" />
             <GridViewColumn Header="Last name" Width="100" />
             <GridViewColumn Header="Birthdate" Width="100" />
          </GridView>
       </ListView.View>
    </ListView>

    Det enklaste sättet att lägga till data till listan är att binda dess kolumner till properties, och sedan binda listan till en samling vars objekt har dessa properties. I XAML uttrycks bindningen med DisplayMemberBinding.

    <ListView x:Name="listView" Margin="5,5,5,5">
       <ListView.View>
          <GridView>
             <GridViewColumn Header="First name"
             Width="100" DisplayMemberBinding="{Binding FirstName}" />
             <GridViewColumn Header="Last name"
             Width="100" DisplayMemberBinding="{Binding LastName}" />
             <GridViewColumn Header="Birthdate"
             Width="100" DisplayMemberBinding="{Binding BirthDate}" />
          </GridView>
       </ListView.View>
    </ListView>

    Så här skulle klassen som beskriver objektet som vi ska placera i listan som ska bindas:

    class Employee
    {
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public DateTime BirthDate { get; set; }
    }

    Slutligen, denna kod utför bindningen. Den kan t.ex. köras i fönstrets Initialized-event:

    var emps = new List<Employee>();
    emps.Add(new Employee() { FirstName = "Sven", LastName = "Svensson", BirthDate = new DateTime(1960, 1, 1) });
    emps.Add(new Employee() { FirstName = "Klas", LastName = "Klasson", BirthDate = new DateTime(1970, 1, 1) });
    emps.Add(new Employee() { FirstName = "Göran", LastName = "Göransson", BirthDate = new DateTime(1980, 1, 1) });
    listView.ItemsSource = emps;

    Förändringar som sker i listan under programmets gång reflekteras först när man anropar metoden Refresh på listans Items-property.

    (listView.ItemsSource as List<Employee>).Add(
       new Employee()
       {
          FirstName = "Pierre",
          LastName = "Fridell",
          BirthDate = new DateTime(1955, 1, 1)
       });
    listView.Items.Refresh();

    Slutligen, om vi vill styra format eller design så kan vi placera en CellTemplate under en GridViewColumn, och placera värdet (eller propertynamnet i detta fall) där. Här använder jag en TextBlock-kontroll. För att centreringen av texten ska fungera, måste jag tala om att alla ListViewItem-objekt ska dras ut till sin fulla storlek.

    <ListView x:Name="listView" Margin="5,5,5,5">
       <ListView.Resources>
          <Style TargetType="ListViewItem">
             <Setter Property="HorizontalContentAlignment" Value="Stretch" />
          </Style>
       </ListView.Resources>
       <ListView.View>
          <GridView>
             <GridViewColumn Header="First name" Width="100" DisplayMemberBinding="{Binding FirstName}" />
             <GridViewColumn Header="Last name" Width="100" DisplayMemberBinding="{Binding LastName}" />
             <GridViewColumn Header="Birthdate" Width="100">
                <GridViewColumn.CellTemplate>
                   <DataTemplate>
                      <TextBlock Text="{Binding BirthDate, StringFormat=\{0:yyyy\}}" TextAlignment="Center"></TextBlock>
                   </DataTemplate>
                </GridViewColumn.CellTemplate>
             </GridViewColumn>
          </GridView>
       </ListView.View>
    </ListView>
  • Lathund XAML: Positionering

    Lathund XAML: Positionering

    För att placera ut en knapp på en absolut position, sätt HorizontalAlignment till “Left” och VerticalAlignment till “Top”. Därefter, använd de första två talen i Margin för X- och Y-position. Exempel:

    <Button x:Name="button" Content="Button"
    HorizontalAlignment="Left" Margin="105,101,0,0"
    VerticalAlignment="Top" Width="75"/>

    Om man istället vill fästa knappen (eller vad det nu må vara för kontroll man jobbar med) över hela det tillgängliga området så används Margin för att ange avstånd från kanten. Här fyller knappen hela området, men lämnar tre pixlars marginal på alla fyra sidor.

    <Button x:Name="button" Content="Button" Margin="3,3,3,3" />

    Det innebär att man ganska enkelt kan bygga ett traditionellt användargränssnitt med en meny högst upp och ett statusfält längst ner enligt följande:

    <Menu x:Name="menu" Height="28" Margin="0" VerticalAlignment="Top" />
    <StatusBar Height="28" Margin="0" VerticalAlignment="Bottom" />

    Om jag nu vill använda det resterande området för t.ex. en knapp, så måste jag öka den övre (andra värdet) och den undre (det fjärde värdet) så att inte knappen skymmer menyn eller statusfältet.

    <Menu x:Name="menu" Height="28" Margin="0" VerticalAlignment="Top" />
    <StatusBar Height="28" Margin="0" VerticalAlignment="Bottom" />
    <Button x:Name="button" Content="Button" Margin="3,31,3,31" />

    Alternativt kan man använda sig av en contaner som hanterar dockningen, en DockPanel. Här anger jag att menyn ska vara fäst högst upp och att statusfältet ska vara längst ner, genom att hänvisa till den. För att knappen ska ta upp det resterande området, räcker det att jag placerar den sist i listan, och sätter attributet LastChildFill till “True”.

    <DockPanel Margin="0,0,0,0" LastChildFill="True">
       <Menu x:Name="menu" DockPanel.Dock="Top" Height="28" />
       <StatusBar DockPanel.Dock="Bottom" Height="29" />
       <Button x:Name="button" Content="Button" Margin="3,3,3,3" />
    </DockPanel>

    En uppenbar fördel med dockningspanelen är att man inte behöver specificera för knappen i mitten (eller vad man nu har för slags kontroll placerad där) hur stor den ovanliggande menyraden är. Och behöver man inte specificera det, så behöver man inte veta det, utan kan låta den ta upp så mycket plats som sitt innehåll, menyerna, kräver.

    <DockPanel Margin="0,0,0,0" LastChildFill="True">
       <Menu x:Name="menu" DockPanel.Dock="Top">
          <MenuItem Header="File" Margin="4,4,4,4">
             <MenuItem Header="Quit" />
          </MenuItem>
       </Menu>
       <StatusBar DockPanel.Dock="Bottom" Height="29" />
       <Button x:Name="button" Content="Button" Margin="3,3,3,3" />
    </DockPanel>
  • Snittvärde

    Snittvärde

    Jag fick en fråga om att räkna ut snittvärden. Det finns olika sätt att lösa det på, där det enklaste kanske är att använda Excel för att göra ett enkelt datablad. Jag tänkte visa hur man kan göra i C# för att få en start till ett registerprogram. För detta använder jag Visual Studio 2015 Community Edition som kan laddas hem gratis här. Välj att skapa ett nytt projekt.

    bild1

    Jag kör mot .NET Framework 4.5.2 (1) och har valt kategorin Windows under C# (2) och projekttypen Console Application (3). Se till att välja ett lämpligt projektnamn och en lämplig plats att spara projektet på (4). Sen är det bara att börja skriva.

    Här har vi den grundläggande principen:

             //Några flyttal att beräkna snitt på.
             double[] tal = { 3.0, 4.0, 1.5, 2.0 };
    
             //Summera talen.
             var summa = 0.0;
             for (int i = 0; i < tal.Length; i++)
                summa += tal[i];
    
             //Dividera med antalet.
             var snitt = summa / tal.Length;
    
             //Presentera resultatet.
             Console.WriteLine(snitt);
    
             //Pausa tills användaren trycker Enter.
             Console.ReadLine();

    Vi borde nu se att snittet av 3.0, 4.0, 1.5 och 2.0 är 2.625.

    Eftersom vi har tillgång till namnrymden System.Linq så kan vi använda en inbyggd funktion för att räkna ut snittet. Detta ger samma resultat:

             //Några flyttal att beräkna snitt på.
             double[] tal = { 3.0, 4.0, 1.5, 2.0 };
    
             //Be om snittet.
             var snitt = tal.Average();
    
             //Presentera resultatet.
             Console.WriteLine(snitt);
    
             //Pausa tills användaren trycker Enter.
             Console.ReadLine();

    Detta kan vi applicera om vi bygger en struktur som innehåller ett flyttal. Följande kod beskriver ett musikalbum med låtar. Låtarna har ett betyg mellan 1 och 10, och deras snitt ger oss ett betyg på musikalbumet.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Snitt
    {
       class Program
       {
          static void Main(string[] args)
          {
             //Konstruera ett album.
             var a = new Album() { AlbumName = "A Night at the Opera",
                                   ArtistName = "Queen" };
             //Lägg på låtar.
             a.Songs.Add(new Song() { TrackNumber = 1,
                                      TrackName = "Death on Two Legs",
                                      Rating = 7.0 });
             a.Songs.Add(new Song() { TrackNumber = 2,
                                      TrackName = "Lazing on a Sunday Afternoon",
                                      Rating = 4.0 });
             a.Songs.Add(new Song() { TrackNumber = 3,
                                      TrackName = "I'm in Love with My Car",
                                      Rating = 6.0 });
    
             //Hämta låtarnas betyg.
             var ratings = (from s in a.Songs
                           select s.Rating).ToArray();
    
             //Hämta ut låtarnas snittvärde och avrunda.
             var snitt = Math.Round(ratings.Average(), 2);
    
             //Presentera resultatet.
             Console.WriteLine(snitt);
    
             //Pausa tills användaren trycker Enter.
             Console.ReadLine();
          }
       }
    
       //Definiera en låt.
       public class Song
       {
          public int TrackNumber { get; set; }
          public string TrackName { get; set; }
          public double Rating { get; set; }
       }
    
       //Definiera ett musikalbum
       public class Album
       {
          public string ArtistName { get; set; }
          public string AlbumName { get; set; }
          public List<Song> Songs { get; set; } = new List<Song>();
       }
    }
    

    Detta ger oss svaret 5.67.

    Klasserna Song och Album kan användas som en del av en Windows-applikation med ett grafiskt användargränssnitt. Projekttypen skulle kunna vara en Windows Forms Application. Att sammanställa en sådan applikation är ett ganska stort och tidskrävande projekt. En album-lista ska skapas, serialisering och deserialisering ska implementeras och det grafiska användargränssnittet ska byggas.

  • Stora tal

    Stora tal

    Det finns ett mycket roligt klipp med karaktären Ali G där han ställer sig frågan om en dator någonsin kan räkna ut ett väldigt stort godtyckligt tal. Han får svaret att alla räkneövningar han kan nämna, kan räknas ut av en modern dator. Din miniräknare kommer förmodligen slå i taket när talen blir för stora, men en vanlig PC med Windows kan ge dig precis vilket svar som helst. Det krävs lite trixande under skalet för att få detta att fungera.

    Heltal representeras normalt av en bit-struktur, alltså ett antal ettor och nollor. Ett 32-bitars heltal är ett heltal som beskrivs av 32 bitar (4 bytes). Bitrepresentationen är liknar binärrepresentationen av ett tal, vilket innebär att både bitrepresentationen av talet 8 och binärrepresentationen av talet 8 är 1000. Ju större tal man vill beskriva, desto fler bitar behöver man ha, och förr eller senare slår man i taket.

    Men det finns en struktur i Windows som heter BigInteger som inte har någon begränsning när det kommer till heltalsrepresentationer och heltalsberäkningar. Det beror på att talen internt lagras som en teckensekvens istället för som ett bitmönster, där varje tecken i sin tur representeras av ett begränsat bitmönster. Då blir det hela en fråga om hur många tecken som får plats i minnet, vilket är väldigt många. För att få se vad BigInteger-strukturen kan göra, kan man skriva in en enkel kod i PowerShell som dubblerar ett tal ett antal gånger. Om jag börjar med talet 1 och dubblar det så får jag 2. Dubblar jag det så får jag 4, 8, 16, 32, 64, 128, 256, och så vidare. Ganska snart blir talet väldigt stort. Redan efter blott 100 dubbleringar landar man på talet 2 535 301 200 456 458 802 993 406 410 752 vars binära representation skulle bli väldigt stor. Men i en BigInteger-struktur är detta bara att betrakta som 31 tecken som avkodas till värden i turordning när beräkningar ska göras.

    Så utrustade med BigInteger-strukturen kan vi se vad som händer när vi dubblerar ett tal 500 gånger. För att göra detta, skriv:

    $x = New-Object System.Numerics.BigInteger 1; for ($i = 0; $i -le 500; $i++) { $x *= 2; Write-Output $x }

    PowerShell levererar svaret på ett ögonblick. Talet 1 dubblat 500 gånger ger oss talet 6 546 781 215 792 283 740 026 379 393 655 198 304 433 284 092 086 129 578 966 582 736 192 267 592 809 349 109 766 540 184 651 808 314 301 773 368 255 120 142 018 434 513 091 770 786 106 657 055 178 752, vilket är ett så stort tal att det nästan är omöjligt att läsa ut och förstå. För att underlätta kan man använda scientific notation. Istället för att skriva ut föregående tal i sin fulla längd, kan man skriva:

    6,55 * 10150

    Eller:

    6,55E150

    Båda varianterna säger oss att talet börjar på 6 och innehåller 150 siffror till därefter, varav de första nästkommande är 55 (avrundat). Det totala antalet siffror i talet är alltså antalet siffror till vänster om decimalavgränsaren plus talet till höger om E. 151 stycken.

    Så hur står sig en etta dubblerad femhundra gånger mot andra stora tal?

    Vårt observerbara Universum uppskattas innehålla 1,7 * 1011 (eller 1,7E11) galaxer. Uppskattningsvis finns 1024 (1E24) stjärnor i Universum.

    Båda dessa tal är alltså relativt små i jämförelse med den etta vi dubblade femhundra gånger. Hur är det med antalet atomer i vårt observerbara Universum? Faktum är att även det är ett relativt litet tal i detta sammanhang, nämligen 4 * 1080 (4E80), alltså en fyra följt av åttio nollor. 400 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000.

    Så låt oss dubbla vår etta femtusen gånger. Även det räknas fram kvickt av PowerShell. Svaret är 282 493 406 427 885 207 367 041 933 403 229 466 733 779 235 036 908 223 362 737 617 171 423 633 968 541 502 511 617 825 263 342 305 274 671 206 416 862 732 165 528 407 676 139 958 676 671 942 371 453 279 846 862 103 555 703 730 798 023 755 999 290 263 414 138 746 996 425 262 647 505 106 222 430 745 688 071 901 801 071 909 721 466 836 906 811 151 133 473 603 131 174 810 929 399 280 998 101 699 398 944 715 801 811 235 142 753 236 456 432 868 426 363 041 983 113 354 252 997 303 564 408 348 123 661 878 478 353 722 682 766 588 036 480 451 677 385 451 192 294 010 288 486 562 150 551 258 990 678 187 626 397 933 471 267 212 659 382 047 684 908 251 671 777 313 746 267 962 574 481 960 017 676 147 336 443 608 528 865 821 788 061 578 040 438 881 156 396 976 534 679 536 477 744 559 804 314 840 614 495 141 020 847 691 737 745 193 471 783 611 637 455 592 871 506 037 036 173 282 712 025 702 605 093 453 646 018 500 436 656 036 503 814 680 490 899 726 366 531 275 975 724 397 022 092 725 970 923 899 174 562 238 279 814 456 008 771 885 761 907 917 633 109 135 250 592 173 833 771 549 657 868 899 882 724 833 177 350 653 880 665 122 207 329 113 965 244 413 668 948 439 622 163 744 809 859 006 963 982 753 480 759 651 997 582 823 759 605 435 167 770 997 150 230 598 943 486 938 482 234 140 460 796 206 757 230 465 587 420 581 985 312 889 685 791 023 660 711 466 304 041 608 315 840 180 083 623 903 760 913 411 030 936 698 892 365 463 484 655 371 978 555 215 241 419 051 756 637 532 976 736 697 930 030 949 995 728 239 530 882 866 713 856 024 688 223 531 470 672 787 115 758 429 874 008 695 136 417 331 917 435 528 118 587 185 775 028 585 687 114 094 178 329 752 966 233 231 383 772 407 625 995 111 380 343 784 339 467 510 448 938 064 950 157 595 661 802 643 159 880 254 674 421 388 754 566 879 844 560 548 121 596 469 573 480 869 786 916 240 396 682 202 067 625 013 440 093 219 782 321 400 568 004 201 960 905 928 079 577 408 670 605 238 675 195 724 104 384 560 742 962 264 328 294 373 028 338 181 834 383 818 752.

    I scientific notation skrivs detta tal 2,82 * 101505 eller 2,82E1505. Med tanke på att vi ännu inte närmat oss någon bortre gräns, så kan man lugnt konstatera att den som äger en vanlig PC förvaltar en svindlande räkningskapacitet.

  • Anropa alla klienter från ASP.NET

    Anropa alla klienter från ASP.NET

    SignalR är ett ramverk som låter webbklienter prata direkt med webbservern via sockets, och som låter webbservern prata direkt med klienterna på samma sätt. Tack vare SignalR kan du göra funktionsanrop direkt till de användare som för tillfället råkar surfa på din hemsida. SignalR bygger ovanpå ramverket OWIN för ASP.NET.

    I detta exempel arbetar jag i Visual Studio 2013 med ett projekt av typen ASP.NET MVC, så jag utgår alltså ifrån Microsofts exempelprogram som levereras som mall i Visual Studio 2013.

    signalr1

    För att komma igång ska vi:

    • Installera paketet SignalR i projektet.
    • Läsa in JavaScript-filen för SignalR.
    • Koppla SignalR-hubbarna till OWIN.
    • Skapa en hubb.

    Därefter är det fritt fram att börja kommunicera!

    För att installera SignalR, skriv följade i Package Manager Console:

    Install-Package Microsoft.AspNet.SignalR

    Eftersom SignalR ligger i version 2.2 när jag skriver detta, så kompletteras mitt program med filerna jquery.signalR-2.2.0.js och jquery.signalR-2.2.0.min.js i mappen Scripts, men tänk på att versionsnumret kan variera om du vill testa detta.

    Därefter ska det minimerade scriptet inkluderas i de vyer som SignalR ska fungera på. Jag hade tänkt mig att kunna använda den överallt, så jag lägger in scriptet i Views/Shared/_Layout.cshtml. Vi ska även läsa in det autogenererade scriptet signalr/hubs.

    <script type="text/javascript"
       src="~/scripts/jquery.signalR-2.2.0.min.js"></script>

    Jag föredrar att flytta upp de script som redan ligger på sidan till dokumentets head-sektion, så att den ser ut så här:

    <head>
       <meta charset="utf-8" />
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>@ViewBag.Title - My ASP.NET Application</title>
       @Styles.Render("~/Content/css")
       @Scripts.Render("~/bundles/modernizr")
       @Scripts.Render("~/bundles/jquery")
       @Scripts.Render("~/bundles/bootstrap")
       <script type="text/javascript"
         src="~/scripts/jquery.signalR-2.2.0.min.js"></script>
       
       @RenderSection("scripts", required: false)
    </head>

    I nästa steg ska vi koppla SignalR-hubbarna till OWIN. Hubbarna, alltså kommunikationsnaven, har ett client-attribut som används för att prata med klienten och ett server-attribut som används för att prata med servern, och identifieras av adressen “/signalr” om vi inte sagt något annat. För att göra kopplingen behöver vi ha en OWIN Startup class. Högerklicka på ditt projektnamn i Solution Explorer, välj att lägga till en OWIN Startup class. Kalla den för OwinStartup. Det ger oss följande kod:

    using System;
    using System.Threading.Tasks;
    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(WebApplication3.OwinStartup))]
    
    namespace WebApplication3
    {
       public class Startup
       {
          public void Configuration(IAppBuilder app)
          {
             // For more information on...
          }
       }
    }

    Ersätt kommentaren i funktionen Configuration med följande anrop:

    app.MapSignalR();

    Nu har vi gjort de förberedelser som behövs för att kommunicera. Till att börja med tänkte jag att klienten ska få skicka ett meddelande till servern när användaren klickar på en knapp.

    Den sista förberedelsen innan vi kan börja är att skapa en hubb. En hubb är en klass som ärver från Microsoft.AspNet.SignalR.Hub. Kontrollera att du har mappen App_Code, annars kan den skapas från menyn som visas när man högerklickar på projektnamnet. Sedan, högerklicka på mappen App_Code och lägg till en klass av typen SignalR Hub Class (v2). Ge den namnet MinTestHub. Det går också att lägga till en vanlig klass och läsa in namnrymden Microsoft.AspNet.SignalR, och sedan ärva från Hub. Här i kan vi skriva de funktioner som vi vill exponera för klienterna, i mitt fall SkickaMeddelande.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Microsoft.AspNet.SignalR;
    
    namespace WebApplication3.App_Code
    {
       public class MinTestHub : Hub
       {
          public void SkickaMeddelande(string meddelande)
          {
             this.Clients.All.servernPratar(
                "Ett meddelande: " + meddelande);
          }
       }
    }

    Visual Studios texteditor är inte speciellt hjälpsam eftersom koden är dynamisk och okänd för Visual Studio, men detta betyder att när en klient får för sig att anropa servern med SignalR, så svarar servern genom att anropa alla klienter.

    Tänk på att hubben exponeras som ett attribut till ett JavaScript-objekt, connection, på klientsidan med första bokstaven som gemen, vilket betyder att denna hubb kommer att heta minTestHub på klienten – inte MinTestHub.

    För att summera klassen MinTestHub: Funktionen SkickaMeddelande exponeras för klienterna och låter dem prata med servern. Servern svarar genom att anropa JavaScript-funktionen servernPratar hos alla klienterna.

    Det som återstår nu är klientkoden. Jag skriver den i applikationens startsida, Views/Home/Index.cshtml.

    På en lämplig plats vill jag ha en knapp kallad SendFromClient och därefter öppnar jag ett script-block.

    <input type="button" id="SendFromClient" value="Send from client" />
    <script type="text/javascript">
    
    </script>

    All kod skriver jag i jQuery:s uppstartevent. Det innebär att hela koden är inkapslad av $(function () { och });. I script-blocket gör jag två saker. Först ansluter jag mig till hubben och tillhandahåller definitionen av servernPratar, alltså skriver vad som ska hända när ett anrop från servern kommer in.

    var testhub = $.connection.minTestHub;
    testhub.client.servernPratar = function (meddelande) {
       alert(meddelande);
    };

    Därefter, när hubben är startad, så talar jag om vad som ska hända när knappen blir klickad på. Då vill jag anropa funktionen skickaMeddelande på servern.

    $.connection.hub.start().done(function () {
       $("#SendFromClient").click(function () {
          testhub.server.skickaMeddelande("Hej från klienten!");
       });
    });

    Hela klientkoden som jag infogar ser alltså ut så här:

    <input type="button" id="SendFromClient" value="Send from client" />
    
    <script type="text/javascript">
       $(function () {
    
          var testhub = $.connection.minTestHub;
          testhub.client.servernPratar = function (meddelande) {
             alert(meddelande);
          };
    
          $.connection.hub.start().done(function () {
             $("#SendFromClient").click(function () {
                testhub.server.skickaMeddelande("Hej från klienten!");
             });
          });
       });
    </script>

    Om du testar att öppna flera webbläsare och surfar till din applikation, kommer du att se att när du klickar på knappen på en av dem, så visar sig meddelanderutan i alla. (För bäst effekt, använd olika webbläsare med tanke på att en meddelanderuta kan blockera andra meddelanderutor.)

    Hubben i din server blir hårt belastad kan meddelanden skickas i okontrollerad ordning. Det beror på att anropen till klienten är asynkrona, men detta kan avhjälpas med async och await.

  • Om aktörer

    Om aktörer

    Det finns tre olika typer av aktörer i Akka .NET. Nedan använder jag en ReceiveActor som utmärker sig genom att den använder funktionen Receive för att ta emot ett meddelande. Exemplen som följer förutsätter att namnrymden Akka.Actor är inläst. Alla aktörer har en sökväg som är oberoende av vilken CLR-klass som beskriver dem. Givet att system är ett initierat aktörssystem med namnet MittSystem och att vi har två aktörer…

    public class EnRootActor : ReceiveActor
    {
    }
    
    public class EnAnnanActor : ReceiveActor
    {
    }

    …så kommer denna kod att fallera eftersom båda aktörerna, trots sina olika klasser, gör anspråk på samma sökväg:

    var a = system.ActorOf<EnRootActor>("MinRoot");
    Console.WriteLine(a.Path);
    var b = system.ActorOf<EnAnnanActor>("MinRoot");
    Console.WriteLine(b.Path);

    Jag använder Akka .NET lokalt, vilket syns på sökvägen som är akka://MittSystem/user/MinRoot vilket innebär protokollet akka, aktörssystemet MittSystem följt av sökvägen user/MinRoot. Alla användardefinierade aktörer ligger under user.

    Både a och b kan samexistera givet att vi ger dem unika namn.

    Aktörer behöver inte skapas på rootnivå. Om en aktör skapar en aktör organiseras den nya aktören under den som skapar aktören.

    public class EnRootActor : ReceiveActor
    {
       public EnRootActor()
       {
          this.Receive<Message>(m =>
          {
             var c = Context.ActorOf<EnAnnanActor>("Child");
             Console.WriteLine(c.Path);
          });
       }
    }

    I ovanstående fall har c sökvägen akka://MittSystem/user/MinRoot/MinChild, och eftersom vi har ett villkorslöst skapande så stöter vi på problem nästa gång vi skickar ett meddelande till EnRootActor eftersom MinChild inte kan skapas mer än en gång.

    Akka .NET erbjuder möjligheten att erhålla referenser till existerande aktörer genom funktionen ActorSelection som kan agera antingen på systemet eller i kontexten för någon existerande aktör.

    I detta exempel (en Console Application) är skapas en tredje aktör när aktör a hanterar meddelandet som skickas till den. Den tredje aktören får sedan ett meddelande skickat till sig från Main, vilket är en annan tråd.

    class Program
    {
       private static ActorSystem system;
    
       static void Main(string[] args)
       {
          var system = ActorSystem.Create("MittSystem");
          var a = system.ActorOf<EnRootActor>("MinRoot");
          Console.WriteLine(a.Path);
          var b = system.ActorOf<EnAnnanActor>("EnTillRoot");
          Console.WriteLine(b.Path);
          a.Tell(new Message("Helloooooo!"));
    
          //Godtycklig paus eftersom childaktören
          //skapas på en annan tråd.
          System.Threading.Thread.Sleep(1000);
    
          //Erhåll en referens till childaktören
          //och skicka ett meddelande.
          system.ActorSelection(
             "akka://MittSystem/user/MinRoot/Child").Tell(
             new Message("Snodd ref."));
    
          Console.ReadLine();
       }
    }
    
    public class EnRootActor : ReceiveActor
    {
       public EnRootActor()
       {
          this.Receive<Message>(m =>
          {
             Console.WriteLine(m.Text);
             //Skapa en ny.
             var c = Context.ActorOf<EnAnnanActor>("Child");
             Console.WriteLine(c.Path);
          });
       }
    }
       
    public class EnAnnanActor : ReceiveActor
    {
       public EnAnnanActor()
       {
          this.Receive<Message>(m => Console.WriteLine(m.Text));
       }
    }
    
    public class Message
    {
       public string Text { get; private set; }
       public Message(string text)
       {
          this.Text = text;
       }
    }

    För att inte programmet ska krascha nästa gång EnRootActor tar emot meddelandet, kan man kort och gott kolla om childaktören redan är skapad.

    public class EnRootActor : ReceiveActor
    {
       public EnRootActor()
       {
          this.Receive<Message>(m =>
          {
             Console.WriteLine(m.Text);
    
             //Skapa en ny om det behövs.
             if (Context.GetChildren().Count() <= 0)
             {
                var c = Context.ActorOf<EnAnnanActor>("Child");
                Console.WriteLine(c.Path);
             }
          });
       }
    }

    Skulle man till äventyrs vara intresserad av vilka aktörer som är childaktörer går det bra att läsa av deras sökväg eller kanske bara deras namn:

    foreach (var x in Context.GetChildren())
       Console.WriteLine(x.Path.Name);

    Så här ser samma exempel ut i Visual Basic (förutsatt att du lagt på Akka .NET med NuGet):

    Imports Akka.Actor
    
    Module Module1
    
       Private sys As ActorSystem
    
       Sub Main()
          sys = ActorSystem.Create("MittSystem")
          Dim a = sys.ActorOf(Of EnRootActor)("MinRoot")
          Console.WriteLine(a.Path)
          Dim B = sys.ActorOf(Of EnAnnanActor)("EnTillRoot")
          a.Tell(New Message("Helloooooo!"))
          System.Threading.Thread.Sleep(1000)
          sys.ActorSelection( _
             "akka://MittSystem/user/MinRoot/Child").Tell( _
             New Message("Snodd ref."))
          a.Tell(New Message("Tjo!"))
          Console.ReadLine()
       End Sub
    
    End Module
    
    Public Class EnRootActor
       Inherits ReceiveActor
    
       Public Sub New()
          Me.Receive(Of Message)(AddressOf Me.ReceiveMessage)
       End Sub
    
       Private Sub ReceiveMessage(m As Message)
          Console.WriteLine(m.Text)
          If Context.GetChildren().Count() <= 0 Then
             Dim c = Context.ActorOf(Of EnAnnanActor)("Child")
             Console.WriteLine(c.Path)
          End If
       End Sub
    
    End Class
    
    Public Class EnAnnanActor
       Inherits ReceiveActor
    
       Public Sub New()
          Me.Receive(Of Message)(AddressOf Me.ReceiveMessage)
       End Sub
    
       Private Sub ReceiveMessage(m As Message)
          Console.WriteLine(m.Text)
       End Sub
    
    End Class
    
    Public Class Message
    
       Public Property Text() As String
    
       Public Sub New(Text As String)
          Me.Text = Text
       End Sub
    
    End Class

    Förutom ReceiveActor som alltså anropar funktionen Receive för att ta emot meddelanden, så har vi även TypedActor och UntypedActor.

    En TypedActor anropar inte funktionen Receive för att ta emot meddelanden, utan har en överlagring av Handle för varje meddelandetyp som aktören kan hantera. En TypedActor ärver därför inte bara från basklassen TypedActor, utan även från det generiska interfacet IHandle<> för varje meddelandetyp som aktören ska hantera. Om man jobbar med klassen TypedActor betalar man med lite minskad flexibilitet, men vinner i ökad struktur. I detta exempel kan aktören ta emot både meddelande av typen A och B (där A och B är vanliga CLR-klasser).

    class Program
    {
       private static ActorSystem system;
    
       static void Main(string[] args)
       {
          var system = ActorSystem.Create("MittSystem");
          var actor = system.ActorOf<MyTypedActor>("MinActor");
          actor.Tell(new A());
          actor.Tell(new B());
          Console.ReadLine();
       }
    }
    
    public class MyTypedActor : TypedActor, IHandle<A>, IHandle<B>
    {
       public void Handle(A message)
       {
          Console.WriteLine("Tack för A!");
       }
    
       public void Handle(B message)
       {
          Console.WriteLine("Tack för B!");
       }
    }
    
    public class A
    {
    }
    
    public class B
    {
    }

    En UntypedActor fungerar enligt samma princip, fast utan typer. Den har en enda funktion som blir anropad vid inkommande meddelande, och den funktionen tar emot meddelandet i object-format. Jag personligen kan inte se någon speciell poäng i att välja att ärva från UntypedActor, för om man inte har behov av typlåsningen så är ReceiveActor ett bättre val. Notera att UntypedActor skriver över funktionen OnReceive.

    class Program
    {
       private static ActorSystem system;
    
       static void Main(string[] args)
       {
          var system = ActorSystem.Create("MittSystem");
          var actor = system.ActorOf("MinActor");
          actor.Tell(new A());
          actor.Tell(new B());
          Console.ReadLine();
       }
    }
    
    public class MyUntypedActor : UntypedActor
    {
       protected override void OnReceive(object message)
       {
          if (message.GetType() == typeof(A))
             Console.WriteLine("Tack för A!");
          else if (message.GetType() == typeof(B))
             Console.WriteLine("Tack för B!");
       }
    }
    
    public class A
    {
    }
    
    public class B
    {
    }
  • Schemalagda meddelanden i Akka .NET

    Schemalagda meddelanden i Akka .NET

    Denna gång tänker jag använda en Windows Forms Application för att visa schemaläggaren i Akka .NET. Som vanligt, för att komma åt Akka .NET i ditt projekt, växla till fönstret Package Manager Console och skriv Install-Package Akka. Om du inte ser Package Manager Console, klicka på rullgardinsmenyn Tools, välj Library Package Manager och Package Manager Console.

    Programmet består av följande meddelandeklass:

    public class TimedMessage
    {
       public string Message { get; private set; }
       public TimedMessage(string message)
       {
          this.Message = message;
       }
    }

    …och följande aktör:

    public class Listen : ReceiveActor
    {
       public Listen()
       {
          this.Receive<TimedMessage>(x =>
          {
             System.Diagnostics.Debug.WriteLine(x.Message);
          });
       }
    }

    Huvudfönstret har läst in namnrymden Akka.Actor för att några extension methods ska vara tillgängliga…

    using Akka.Actor;

    …och innehåller en knapp som heter button1. I formulärets Load-event skapas aktörssystemet och aktören och under knappen så skickar jag ett meddelande (TimedMessage) till aktören.

    public partial class Form1 : Form
    {
       private ActorSystem actorSystem;
       private IActorRef listen;
    
       public Form1()
       {
          InitializeComponent();
       }
    
       private void button1_Click(object sender, EventArgs e)
       {
          this.listen.Tell(new TimedMessage("Helloooo!"));
       }
    
       private void Form1_Load(object sender, EventArgs e)
       {
          this.actorSystem = ActorSystem.Create("ActorSystem");
          this.listen = this.actorSystem.ActorOf(
             Props.Create(typeof(Listen)));
       }
    }

    Om du testar detta lilla exempel så märker du att meddelandet går iväg till aktören i samma ögonblick som du klickar på knappen. Men istället för att anropa aktörens Tell-funktion i Click-eventet, så kan vi använda aktörssystemets Scheduler-property. Den har ett antal funktioner för schemaläggning och upprepning. Följande förändring gör att meddelandet går iväg först tre sekunder efter att man klickar på knappen.

    private void button1_Click(object sender, EventArgs e)
    {
       this.actorSystem.Scheduler.ScheduleTellOnce(
          TimeSpan.FromSeconds(3), this.listen,
          new TimedMessage("Helloooo!"), ActorRefs.Nobody);
    }

    Funktionen ScheduleTellOnce schemalägger ett meddelande istället för att skicka iväg det direkt. Det är det första argumentet som bestämmer när meddelandet ska gå iväg. Uttrycket TimeSpan.FromSeconds(3) säger att meddelandet ska gå iväg efter tre sekunder, oavsett vad klockan är. Schemaläggaren förväntar sig att man anger relativa tider, så om jag vill att meddelandet ska gå iväg ett bestämt klockslag, t.ex. den 19/5 kl. 13:30, kan jag istället skriva new DateTime(2015, 5, 19, 13, 30, 0).Subtract(DateTime.Now).

    Det andra argumentet är aktören som ta emot meddelandet. I mitt exempel finns endast en aktör, så den skickar jag in. Det tredje argumentet är meddelandet som ska skickas. Slutligen, det fjärde argumentet är avsändaren. Om det är en aktör som skickar meddelandet, skriver man normalt this.Self, men det går bra att skicka in en annan aktör här.

    I mitt fall när det inte finns någon aktör som avsändare (det är ju formuläret som skickar) så kan det vara frestande att skicka in null, men i dagsläget fungerar inte det. Klassen ActorRefs innehåller konstanter som kan användas som substitut för en avsändare, och Nobody betyder just ingen. (Detta kan komma att ändras.)

    Men tänk dig att du vill bli informerad när något inte händer. Från första klicket vill du veta om användaren fortsätter att klicka var tredje sekund, eller om användaren har blivit passiv. Det kan vi lösa på följande vis:

    1. När första klicket inträffar, säger vi att vi vill ha ett meddelande om tre sekunder.

    2. När knappen blir klickad på nästa gång, inom tre sekunder, så avbryter vi beställningen, och gör om den på nytt.

    3. Skulle användaren inte klicka inom tre sekunder så går meddelandet iväg eftersom det inte hunnit avbrytas. Således vet vi att användaren har blivit passiv.

    Detta kräver minimala förändringar. Först vill vi ha ett kvitto på beställningen. För detta använder jag en medlemsvariabel av typen Akka.Actor.ICancelable.

    private ICancelable idle_detector = null;

    Funktionen ScheduleTellOnce ger ingen retur, men ScheduleTellOnceCancelable ger ett svar i ICancelable-format som jag kan lagra i min nya medlemsvariabel. När användaren klickar, kan jag kolla om det finns en liggande beställning, och i så fall kan jag avbryta den – eftersom användaren klickat så är han ju inte inaktiv.

    private void button1_Click(object sender, EventArgs e)
    {
       if (!(this.idle_detector == null))
          this.idle_detector.Cancel();
       this.idle_detector =
          this.actorSystem.Scheduler.ScheduleTellOnceCancelable(
          TimeSpan.FromSeconds(3), this.listen,
          new TimedMessage("Idle!!!"), ActorRefs.Nobody);
    }

    För att testa programmet, starta och klicka lite då och då på knappen. För varje gång tre sekunder får passera utan något klick så går meddelandet iväg till aktören.