Introduktion

I javascript finns något som kallas på destructuring vilket innebär att man kan direkt tilldela separata variabler värdena hos egenskaper hos ett objekt. Läs om det här.

I C# 7.0 och senare finns ungefär motsvarande konstruktion till hands men där kallar man det istället deconstructing. I det enklaste av fallen, där man använder sig av tupler, finns möjligheten till deconstructing direkt i språket. I andra fall, såsom egendefinierad referenstyper, måste man skriva lite kod för att uppnå samma resultat. Läs om deconstructing i C# här.

Nedan följer ett antal exempel på deconstructing, som kan ge en kompakt och samtidigt läsbar och robust kod.

Kod

Deconstructing av tupler

Det här är det enkla fallet, där C# genom sin tuple-typ, har deconstructing-stöd i språket direkt.

static void DeconstructTuples()
{
    var successTuple = (true, new Person(), 0);
    var (isSuccess1, successModel, successStatus) = successTuple;

    var failTuple = (false, default(Person), 123);
    var (isSuccess2, failModel, failStatus) = failTuple;

    // Ignore/discard things
    var (success1, model, _) = (true, new Person(), 0);
    var (success2, _, status) = (false, default(Person), 123);
}

public class Person { ... }

Här ser vi två olika fall av deconstructing, där tuplen består av (bool, object, int). Booleanen indikerar success = true/false, objektet är en returnerad modell/default av modellen och heltalet är en status. Variablerna till vänster i tilldelningar, t.ex. isSuccess1, successModel och successStatus är direkt tillgängliga för användning i koden.

På dom två sista raderna i exemplet ser man även möjligheten att ignorera fält i tuplen. Detta gör man genom att ange “variabelnamnet” _ (underscore/understreck).

Deconstructing av egendefinierade typer

I exemplet nedan ser man hur deconstructing av den egendefinierade typen ResultAsClass<T> ser ut vid användning. Syntaxen ser precis ut som i tuple-fallet ovan, med den enda skillnaden som är new-operatorn vid själva skapandet av objektet.

Deconstructing-beteende hos den egendefinierade typen åstadkommer man genom att implementera funktionen Deconstruct som syns i form av en medlemsfunktion i klassen, sist i exemplet.

Ignore/discard uppnår man på samma sätt som i tuple-exemplet ovan, mha _ (underscore/understreck).

static void DeconstructResultAsClassesWithClassModel()
{
    var successClass = new ResultAsClass<Person>(true, new Person(), 0);
    var (isSuccess1, successModel, successStatuts) = successClass;

    var failClass = new ResultAsClass<Person>(false, default, 123);
    var (isSuccess2, failModel, failStatus) = failClass;

    // Ignore/discard things
    var (success1, model, _) = new ResultAsClass<Person>(true, new Person(), 0);
    var (success2, _, status) = new ResultAsClass<Person>(false, default, 123);
}
  
public class Person { ... }
  
public class ResultAsClass<T>
{
    private bool IsSuccess { get; }
    private int Status { get; }
    private T Model { get; }

    public ResultAsClass(bool isSuccess, T model, int status)
    {
        IsSuccess = isSuccess;
        Model = model;
        Status = status;
    }

    public void Deconstruct(out bool success, out T model, out int status)
    {
        success = IsSuccess;
        model = Model;
        status = Status;
    }
}

Deconstruct-metoden är något som implementatören själv består detaljerna i, så länge signaturen följer mönstret public void Deconstruct(out ...) så kommer den att göra jobbet. Se exemplet nedan på andra deconstruct-implementationer:

public class SomeClass
{
    public SomeClass() { }

    public void Deconstruct(out bool ticksIsEven, out DateTimeOffset at)
    {
        var  utcNow = DateTimeOffset.UtcNow;

        ticksIsEven = utcNow.Ticks % 2 == 0;
        at = utcNow;
    }

    public void Deconstruct(out int min, out int max, out int[] readings)
    {
        var random = new Random((int)DateTimeOffset.UtcNow.Ticks);

        readings = Enumerable.Range(1, 100).Select(x => random.Next(-100, 100)).ToArray();
        min = readings.Min();
        max = readings.Max();
    }
}  
  
static void DeconstructingSpecial()
{
    // Deconstructing with DateTimeOffset
    var (isSuccess, at) = new SomeClass();

    // Deconstructing odd use case
    var (min, max, readings) = new SomeClass();
}

Deconstructing av records

I C#9 infördes en ny referenstyp som har samma beteende som en värdetyp, record. I record byggde man även in direkt stöd för deconstruting, dvs man behöver INTE implementera någon deconstruct-metod i typen. Detta gör alltså att deconstruting uppnås som en kombination av tupler och egendefinierade typer.

Exemplet nedan visar hur deconstructing ser ut, med record, tillsammans med själva record-typen sist i exemplet.

static void DeconstructResultAsRecordWithClassModel()
{
    var successRecord = new ResultAsRecord<Person>(true, new(), 0);
    var (isSuccess1, successModel, successStatuts) = successRecord;

    var failRecord = new ResultAsRecord<Person>(false, default, 123);
    var (isSuccess2, failModel, failStatus) = failRecord;

    // Ignore/discard things
    var (success1, model, _) = new ResultAsRecord<Person>(true, new(), 0);
    var (success2, _, status) = new ResultAsRecord<Person>(false, default, 123);
}
 
public class Person { ... }

public record ResultAsRecord<T>(bool Success, T Model, int Status);

Ignore uppnås på samma sätt som i dom tidigare exemplen, genom _ (underscore/understreck).

Avslutning

Hoppas det här har väckt en smula lust att skriva kompakt och effektiv kod och att det även har väckt lusten att undersöka C#-språkets andra finurliga konstruktioner.

Komplett kod för exemplen ovan finns här.

Lämna en kommentar