Microsoft is al een paar jaar bezig met een nieuwe compiler voor C# en Visual Basic. Deze nieuwe compiler is open source waardoor de implementatie en de voortgang voor iedereen zichtbaar is op https://github.com/dotnet/roslyn. Door de nieuwe compiler wordt het voor het C# en Visual Basic ontwerpteam bij Microsoft makkelijker om nieuwe taalfeatures toe te voegen. En eventueel weer weg te halen. De inspanning om te experimenteren met een feature is dus flink verminderd. In dit artikel kijken we naar een reeks vernieuwingen waar momenteel aan gewerkt wordt.

Een waarschuwing is op zijn plaats: sommige opties worden misschien weer geschrapt en andere opties komen er weer bij. Op https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md zie je een actueel overzicht van de status van iedere voorgestelde feature.

Om te werken met de nieuwe compiler heb je twee mogelijkheden:

  1. Download en installeer de laatste CTP versie van Visual Studio op http://www.visualstudio.com/en-us/downloads/visual-studio-14-ctp-vs.aspx
  2. Maak gebruik van een geprepareerde virtuele machine op Windows Azure.

De tweede optie is eenvoudiger, mits je beschikt over een Azure account. Je kunt hiervoor ook een gratis proefaccount aanmaken op http://azure.microsoft.com/en-us/pricing/free-trial/. Wanneer je de CTP versie op je eigen laptop/PC wilt installeren, zorg dan voor een machine die helemaal geschoond kan worden. Zoals Microsoft het stelt: “Installing a CTP release will place a computer in an unsupported state. For that reason, we recommend only installing CTP releases in a virtual machine, or on a computer that is available for reformatting.” Oppassen dus.

De Roslyn compiler is bij het starten van Visual Studio niet direct actief. Om kennis te maken met de nieuwe mogelijkheden moeten we, vooralsnog, in de projectdefinitie het volgende element toe te voegen:

<LangVersion>experimental</LangVersion>

Dit doe je in Visual Studio via Project > Unload Project. Daarna kan je via het contextmenu op het project in de Solution Explorer kiezen voor Edit [projectnaam].csproj. Het bovengenoemde element zet je in iedere PropertyGroup.

Daarna kies je voor Project > Reload Project en je bent klaar om te experimenteren.

 

Gebruik van using voor static classes

Wanneer je veelvuldig een bepaalde static class gebruikt in je code, dan moet je die naam telkens volledig intypen om te methoden op die class te kunnen gebruiken. Het zou weer wat tikwerk schelen als we deze static class gewoon in een using statement kunnen opnemen. Zodoende hoeven we niet steeds de volledige naam te typen.

 

using System;
using System.Convert;
using System.Console;

class Program
{
    static void Main(string[] args)
    {
        WriteLine(ToDateTime("2014-09-27"));
        WriteLine(ToDecimal("5.34"));
    }
}

In de bovenstaande code zie je dat we zowel de class Convert als Console eenmalig refereren. Maar we kunnen daarna zonder expliciete verwijzing naar deze class een methode ervan aanroepen. Zolang er geen verwarring mogelijk is, kan je ook een static class met dezelfde naam toevoegen.

 

using System;
using System.Convert;
using System.Console;
using Convert;

class Program
{
    static void Main(string[] args)
    {
        WriteLine(ToDateTime("2014-09-27"));
        WriteLine(ToDecimal("5.34"));
        WriteLine(ToCustomString(5.4));
    }
}

static class Convert
{
    public static string ToCustomString(double value)
    {
        return string.Format("Waarde is: " + value);
    }
}

Het ligt voor de hand dat wanneer de compiler niet kan onderscheiden tussen methoden met dezelfde naam en parameters er een waarschuwing verschijnt.

 

Primary constructors

(NB 3 oktober 2014: Deze feature is weer geschrapt!)

Hoe vaak komt het voor dat je een constructor voor een class maakt enkel om de belangrijkste eigenschappen van die class te vullen. Het kunnen verplichte eigenschappen zijn zonder welke een object van het betreffende type geen functie heeft.  Dat ziet er meestal ongeveer als volgt uit:

    public class Boek
    {
        private string _titel;
        private string _auteur;
        public Boek(string titel, string auteur)
        {
            _titel = titel;
            _auteur = auteur;
        }
    }

Het zou makkelijker zijn als we die constructor niet expliciet hoeven te schrijven als dit een minimum vereiste is voor de betreffende class. Een snellere schrijfwijze levert ons dit op:

    public class Boek(string titel, string auteur)
    {
        private string _titel = titel;
        private string _auteur = auteur;
    }

De parameters van de primaire constructor vinden we nu terug in de declaratie van de class. Vervolgens kunnen we de eigenschappen _titel en _auteur gewoon gebruiken als iedere andere variabele. Heb je meer constructors nodig, dan blijft de primaire, meest gebruikte, onderdeel van de declaratie, en definieer je andere constructors zoals je gewend bent.

    public class Boek(string titel, string auteur)
    {
        private string _titel = titel;
        private string _auteur = auteur;
        private string _uitgever;
        public Boek(string titel, string auteur, string uitgever) : this(titel, auteur)
        {
            _uitgever = uitgever;
        }
    }

Er is voor de runtime overigens geen verschil. De compiler maakt van deze opzet gewoon twee constructors zoals altijd. Het is dus alleen een handigheidje waardoor je met minder coderegels hetzelfde doel kunt bereiken.

 

Auto-property initializers

Eigenschappen zijn zo veel gebruikt in C# dat je je bijna geen efficiëntie-winst meer kunt voorstellen. Maar omdat we primary constructors hebben in C# 6 hebben we ook een manier nodig om eigenschappen te initialiseren in de class. In het eerdere voorbeeld waren de variabelen _titel en _auteur geen eigenschappen maar velden in de class. Maar als we er een property van willen maken, en we willen ook de auto-property feature gebruiken, dan komt dat er zo uit te zien:

    public class Boek(string titel, string auteur)
    {
        public string Titel { get; set; } = titel;
    }

Dat werkt natuurlijk ook bij een readonly property

   public string Auteur { get; } = auteur;

Auto-properties krijgen door de compiler een achterliggende veld toegewezen. En ook hier zorgt de compiler ervoor dat dit veld de juiste waarde krijgt. Opnieuw hebben we hier niet te maken met een enorme vooruitgang maar het scheelt wel een hoop coderegels.

 

Dictionary initializer

Blijkbaar was er behoefte aan een manier om een Dictionary te initialiseren zoals we dat al kunnen met arrays en lists. De “oude” manier om een Dictionary te declareren en gelijk te vullen is als volgt.

            Dictionary<string, double> temperaturen = newDictionary<string, double>()
            {
                { "Amsterdam", 16.5 },
                { "Den Haag", 18.4 },
                { "Rotterdam", 17.5 }
            };

 

In C# 6 wordt het mogelijk om dit als volgt te schrijven.

 

        Dictionary<string, string> temperaturen = newDictionary<string, string>()
        {
                    ["Amsterdam"] = "16.5",
                    ["Den Haag"] = "18.4",
                    ["Rotterdam"] = "17.5"
        };

Geen bijzondere verbetering, maar het is meer consistent met de manier waarop andere “collecties” geïnitialiseerd kunnen worden. NB: deze optie werkt momenteel niet in de CTP(3), alhoewel het team zegt dat het geïmplementeerd is. We moeten maar geloven dat het uiteindelijk wel zo gaat werken.

 

Declaration expressions

(NB 3 oktober 2014: Deze feature is weer geschrapt!)

Als je gebruik maakt van het out sleutelwoord, dan weet je dat je de variabele eerst moet declareren voordat je deze in de methode kunt gebruiken.

        public static void BerekenTotaal(double prijs, int aantal, outdouble totaal)
        {
            totaal = prijs * aantal;
        }

 

Om de methode BerekenTotaal te kunnen gebruiken moeten we eerst een variabele voor prijs, aantal en totaal declareren.

            double prijs = 20;
            int aantal = 2;
            double totaal = 0;
            BerekenTotaal(prijs, aantal, out totaal);

Het zou mooi zijn als we de variabele totaal niet eerst hoeven te declareren. Dat gaat dus ook als volgt veranderen:

            BerekenTotaal(prijs, aantal, outdouble totaal);

Opnieuw doet de compiler het werkt dat de programmeur tot nu toe moet doen. De compiler zorgt er voor dat de variabele alsnog op de gewenste plek komt te staan.

 

Await in catch/finally

In C# 5 werd async en await geïntroduceerd als een handige manier om asynchrone code te schrijven. Code dus waarbij de uitvoering van code niet geblokkeerd als een methode wat langer duurt, maar waarbij we als ontwikkelaar min of meer op synchrone wijze kunnen programmeren.

Het is inmiddels steeds gebruikelijker geworden om asynchrone APIs aan te spreken. Door C# 5 en .NET 4.5 is dat vrij eenvoudig. Het is echter niet mogelijk om het await keyword te gebruiken in een catch en/of finally blok.

Het C# ontwikkelteam kreeg dat eerst niet voor elkaar. Maar voor versie 6 zijn ze erachter hoe het kan werken. De details van de implementatie zijn ingewikkeld maar daar hebben wij geen last van, wij benutten slechts de feature. Nu kan het dus wel.

        public async void VerwerkData(object data)
        {
            try
            {
                // Verwerk veel gegevens
                awaitTask.Delay(1000);
            }
            catch (Exception)
            {
                awaitTask.Delay(100);
                throw;
            }
        }

 

Exception filters

Visual Basic heeft ze al, en F# ook. Eindelijk heeft C# (in versie 6 dan) ook Exception Filters. Een exception filter is flexibeler dan de normale exceptionhandling hierarchie die gaat van specifiek naar generiek zoals we gewend zijn:

System.IO.FileNotFoundException => System.IO.IOException => System.SystemException => System.Exception        

Met een Exception Filter kunnen we extra criteria opgeven voordat de exceptie in de catch handler wordt afgehandeld.

            try
            {
                throw new Exception("Error from user");
            }
            catch (Exception e) if (e.Message.Contains("user"))
            {
                // Gebruiker deed het fout
                throw;
            }
            catch (Exception e) if (e.Message.Contains("dev"))
            {
                // Ontwikkelaar deed het fout               
                // ssst.
            }

Het gebruik van Exception Filters wordt aangeraden omdat de stack intact blijft. Wanneer de stack later alsnog wordt bewaard, dan kan je precies zijn waar de fout begonnen is. Je kunt ook er ook een handige logfeature van maken die een fout wel registreert maar de flow van het programma niet onderbreekt.

        static bool Log(Exception e)
        {
            // log de fout
            return false;
        } 
            try
            {
            }
            catch (Exception e) if(Log(e))
            {               
            }

 

Null-conditional operators

Een veel voorkomende fout in code is de NullReferenceException. In een wat complexere objecthiërarchie is het niet alleen de fout zelf, maar ook de bron van de fout die de ontwikkelaar nog wel eens voor hoofdbrekens zorgt. Een dergelijke hiërarchie ziet er bijvoorbeeld zo uit.

    public class Boek
    {
        public string Titel { get; set; }
        public List<Auteur> Auteurs { get; set; }
    }

    public class Auteur
    {
        public string Naam { get; set; }
        public Adres WoonAdres { get; set; }
    }

    public class Adres
    {
        public string Woonplaats { get; set; }
    }

Nu zouden we bijvoorbeeld de woonplaats van de eerste auteur van het boek willen opvragen.

     string plaatsnaam = boek.Auteurs.First().WoonAdres.Woonplaats;

Maar willen we voorkomen dat boek null is, of Auteurs null of leeg, of geen WoonAdres bekend is, dan kan kunnen we dit in C# 6 schrijven als:

      string plaatsnaam = boek?.Auteurs?.First()?.WoonAdres?.Woonplaats;

Dat scheelt een hoop if-statements.

 

Eind deel 1

In dit artikel hebben we gekeken naar de features in C# 6 die het waarschijnlijk wel gaan halen in de definitieve versie die we volgend jaar mogen verwachten. Ze zijn immers al geïmplementeerd. Maar het ontwikkelteam bij Microsoft is nog niet klaar, en wacht en hoopt ook nog op feedback. In het volgende deel gaan we kijken naar de features die nog op de planning staan. Dat wil zeggen, op de planning in september 2014. Zodra er een nieuwe CTP-versie uitkomt, kan de lijst zo maar veranderd zijn. Houd https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md dus goed in de gaten.