Tupplar och anonyma typer

Tupplar ger en enkel datastruktur för flera medlemmar i en enda struktur. De är det bästa valet jämfört med anonyma typer. Tupplar ger bättre prestanda, stödjer dekonstruktion och erbjuder en mer flexibel syntax.

Anonyma typer är ett bekvämt sätt att kapsla in en uppsättning skrivskyddade egenskaper i ett enda objekt utan att uttryckligen behöva definiera en typ först. Kompilatorn genererar typnamnet och är inte tillgängligt på källkodsnivå. Kompilatorn härleder typen av varje egenskap. Använd anonyma typer främst när du behöver stöd för uttrycksträd eller när du arbetar med kod som kräver referenstyper.

Tupplar jämfört med anonyma typer

Med både tupplar och anonyma typer kan du gruppera flera värden utan att definiera en namngiven typ. Tupplar har dock bättre språkstöd och kompilering till en effektivare datastruktur. I följande tabell sammanfattas de viktigaste skillnaderna:

Funktion Anonyma typer Tupler
Typ Referenstyp (class) Värdetyp (struct)
Performance Heap-allokering Stackallokering (bättre prestanda)
Mutability Skrivskyddade egenskaper Föränderliga fält
Dekonstruktion Stöds inte Understödd
Uttrycksträd Understödd Stöds inte
Åtkomstmodifierare internal public
Medlemsnamn Krävs eller härleds Valfritt (med standardnamn som Item1, Item2)

När du ska använda tupplar

Använd tupplar när:

  • Du behöver bättre prestanda genom stackallokering.
  • Du vill dekonstruera värden i separata variabler.
  • Du returnerar flera värden från en metod.
  • Du behöver inte stöd för uttrycksträd.

Följande exempel visar hur tupplar erbjuder funktionalitet liknande anonyma typer men med en renare syntax.

// Tuple with named elements.
var tupleProduct = (Name: "Widget", Price: 19.99M);
Console.WriteLine($"Tuple: {tupleProduct.Name} costs ${tupleProduct.Price}");

// Equivalent example using anonymous types.
var anonymousProduct = new { Name = "Widget", Price = 19.99M };
Console.WriteLine($"Anonymous: {anonymousProduct.Name} costs ${anonymousProduct.Price}");

Tupldekonstruktion

Du kan dekonstruera en tuppel i separata variabler, vilket är ett bekvämt sätt att arbeta med enskilda tuple-element. C# stöder flera sätt att dekonstruera tupplar:

static (string Name, int Age, string City) GetPersonInfo()
{
    return ("Alice", 30, "Seattle");
}
// Deconstruct using var for all variables
var (name, age, city) = GetPersonInfo();
Console.WriteLine($"{name} is {age} years old and lives in {city}");
// Output: Alice is 30 years old and lives in Seattle

// Deconstruct with explicit types
(string personName, int personAge, string personCity) = GetPersonInfo();
Console.WriteLine($"{personName}, {personAge}, {personCity}");

// Deconstruct into existing variables
string existingName;
int existingAge;
string existingCity;
(existingName, existingAge, existingCity) = GetPersonInfo();

// Deconstruct and discard unwanted values using the discard pattern (_)
var (name2, _, city2) = GetPersonInfo();
Console.WriteLine($"{name2} lives in {city2}");
// Output: Alice lives in Seattle

Dekonstruktion är användbart i loopar och mönstermatchningsscenarier:

var people = new List<(string Name, int Age)>
{
    ("Bob", 25),
    ("Carol", 35),
    ("Dave", 40)
};

foreach (var (personName2, personAge2) in people)
{
    Console.WriteLine($"{personName2} is {personAge2} years old");
}

Tupplar som en metodreturtyp

Ett vanligt användningsfall för tupplar är som en metodreturtyp. I stället för att definiera out-parametrar kan du gruppera metodresultat i en tuppel. Du kan inte returnera en anonym typ från en metod eftersom den inte har något namn och returtypen inte kan deklareras.

I följande exempel demonstreras hur du använder tupplar med lexikonuppslag för att returnera konfigurationsintervall.

var configLookup = new Dictionary<int, (int Min, int Max)>()
{
    [2] = (4, 10),
    [4] = (10, 20),
    [6] = (0, 23)
};

if (configLookup.TryGetValue(4, out (int Min, int Max) range))
{
    Console.WriteLine($"Found range: min is {range.Min}, max is {range.Max}");
}
// Output: Found range: min is 10, max is 20

Det här mönstret är användbart när du arbetar med metoder som behöver returnera både en framgångsindikator och flera resultatvärden. Med tuppeln kan du använda namngivna fält (Min och Max) i stället för generiska namn som Item1 och Item2, vilket gör koden mer läsbar och självdokumenterande.

När anonyma typer ska användas

Använd anonyma typer när:

  • Du arbetar med uttrycksträd (till exempel i vissa LINQ-leverantörer (Microsoft Language-Integrated Query).
  • Du behöver objektet för att vara en referenstyp.

Det vanligaste scenariot är att initiera en anonym typ med egenskaper från en annan typ. Anta i följande exempel att det finns en klass med namnet Product. Klassen Product innehåller Color och Price egenskaper, tillsammans med andra egenskaper som du inte är intresserad av:

class Product
{
    public string? Color { get; init; }
    public decimal Price { get; init; }
    public string? Name { get; init; }
    public string? Category { get; init; }
    public string? Size { get; init; }
}

Den anonyma typdeklarationen börjar med operatorn new tillsammans med en objektinitierare. Deklarationen initierar en ny typ som endast använder två egenskaper från Product. Anonyma typer används vanligtvis i -satsen i select ett frågeuttryck för att returnera en mindre mängd data. Mer information om frågor finns i LINQ i C#.

Om du inte anger medlemsnamn i den anonyma typen ger kompilatorn medlemmarna av anonym typ samma namn som den egenskap som användes för att initiera dem. Du anger ett namn för en egenskap som initieras med ett uttryck, som du ser i föregående exempel.

I följande exempel är Color namnen på egenskaperna för den anonyma typen och Price. Instanserna är objekt från samlingen products med Product typer:

var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

Projektionsinitierare med anonyma typer

Anonyma typer stöder projektioninitierare, vilket gör att du kan använda lokala variabler eller parametrar direkt utan att uttryckligen ange medlemsnamnet. Kompilatorn härleder medlemsnamnen från variabelnamnen. I följande exempel visas den här förenklade syntaxen:

// Explicit member names.
var personExplicit = new { FirstName = "Kyle", LastName = "Mit" };

// Projection initializers (inferred member names).
var firstName = "Kyle";
var lastName = "Mit";
var personInferred = new { firstName, lastName };

// Both create equivalent anonymous types with the same property names.
Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}");
Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}");

Den här förenklade syntaxen är användbar när du skapar anonyma typer med många egenskaper:

var title = "Software Engineer";
var department = "Engineering";
var salary = 75000;

// Using projection initializers.
var employee = new { title, department, salary };

// Equivalent to explicit syntax:
// var employee = new { title = title, department = department, salary = salary };

Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}");

Medlemsnamnet härleds inte i följande fall:

  • Kandidatnamnet duplicerar en annan egenskapsmedlem i samma anonyma typ, antingen explicit eller implicit.
  • Kandidatnamnet är inte en giltig identifierare (till exempel innehåller det blanksteg eller specialtecken).

I dessa fall måste du uttryckligen ange medlemsnamnet.

Dricks

Du kan använda .NET-formatregeln IDE0037 för att framtvinga om härledda eller explicita medlemsnamn är att föredra.

Du kan också definiera ett fält med hjälp av ett objekt av en annan typ: klass, struct eller till och med en annan anonym typ. Det gör du genom att använda variabeln som innehåller det här objektet. I följande exempel visas två anonyma typer som använder redan instansierade användardefinierade typer. I båda fallen är fältet product i anonyma typer shipment och shipmentWithBonus av typen Product och innehåller standardvärdena för varje fält. Fältet bonus är av en anonym typ som skapats av kompilatorn.

var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };

När du använder en anonym typ för att initiera en variabel deklarerar du vanligtvis variabeln som en implicit typ av lokal variabel med hjälp av var. Du kan inte ange typnamnet i variabeldeklarationen eftersom endast kompilatorn har åtkomst till det underliggande namnet på den anonyma typen. Mer information om varfinns i Implicit inskrivna lokala variabler.

Du kan skapa en matris med anonymt inskrivna element genom att kombinera en implicit typad lokal variabel och en implicit typad matris, som du ser i följande exempel.

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};

Anonyma typer är class typer som härleds direkt från objectoch du kan inte omvandla dem till någon typ förutom object. Kompilatorn ger ett namn för varje anonym typ, även om programmet inte kan komma åt den. När det gäller den vanliga språkkörningen skiljer sig en anonym typ inte från någon annan referenstyp.

Om två eller flera anonyma objektinitierare i en sammansättning anger en sekvens med egenskaper som är i samma ordning och som har samma namn och typer behandlar kompilatorn objekten som instanser av samma typ. De delar samma kompilatorgenererade typinformation.

Anonyma typer stöder icke-destruktiv mutation i form av med uttryck. Med den här funktionen kan du skapa en ny instans av en anonym typ där en eller flera egenskaper har nya värden:

var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);

Du kan inte deklarera ett fält, en egenskap, en händelse eller returtypen för en metod som en anonym typ. På samma sätt kan du inte deklarera en formell parameter för en metod, egenskap, konstruktor eller indexerare som anonym. Om du vill skicka en anonym typ, eller en samling som innehåller anonyma typer, som ett argument till en metod, kan du deklarera parametern som typ object. Att använda object för anonyma typer motverkar dock syftet med stark skrivning. Om du måste lagra frågeresultat eller skicka dem utanför metodgränsen kan du överväga att använda en vanlig namngiven struct eller klass i stället för en anonym typ.

Equals Eftersom metoderna och GetHashCode för anonyma typer definieras i termer av Equals egenskapernas metoder och GetHashCode är två instanser av samma anonyma typ lika endast om alla deras egenskaper är lika.

Kommentar

Tillgänglighetsnivån för en anonym typ är internal. Därför är två anonyma typer som definierats i olika sammansättningar inte av samma typ. Därför kan instanser av anonyma typer inte vara lika med varandra när de definieras i olika sammansättningar, även om alla deras egenskaper är lika med varandra.

Anonyma typer åsidosätter ToString metoden och sammanfogar namnet och ToString utdata för varje egenskap som omges av klammerparenteser.

var v = new { Title = "Hello", Age = 24 };

Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"

Se även