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