C#typy řazené kolekce členůC# tuple types

C#řazené kolekce členů jsou typy, které definujete pomocí prosté syntaxe.C# tuples are types that you define using a lightweight syntax. Výhody zahrnují jednodušší syntaxi, pravidla pro převody na základě počtu (označovaného jako mohutnost) a typy prvků a konzistentní pravidla pro kopie, testy rovnosti a přiřazení.The advantages include a simpler syntax, rules for conversions based on number (referred to as cardinality) and types of elements, and consistent rules for copies, equality tests, and assignments. V rámci kompromisů nepodporují řazené kolekce členů některé objekty orientované na objekt idiomy přidružené k dědičnosti.As a tradeoff, tuples do not support some of the object-oriented idioms associated with inheritance. Přehled v části o řazených kolekcích členů najdete v článku Co je nového v C# 7,0 .You can get an overview in the section on tuples in the What's new in C# 7.0 article.

V tomto článku se naučíte jazykové pravidla upravující řazené kolekce členů v C# 7,0 a novějších verzích, různých způsobech jejich použití a úvodní doprovodné materiály k práci s řazenými kolekcemi členů.In this article, you'll learn the language rules governing tuples in C# 7.0 and later versions, different ways to use them, and initial guidance on working with tuples.

Poznámka

Nové funkce řazené kolekce členů vyžadují ValueTuple typy.The new tuples features require the ValueTuple types. Je nutné přidat balíček System.ValueTuple NuGet, aby jej bylo možné použít na platformách, které neobsahují tyto typy.You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

To je podobné jako u jiných funkcí jazyka, které závisí na typech dodaných v rámci rozhraní.This is similar to other language features that rely on types delivered in the framework. Příklady zahrnují async a await spoléhají INotifyCompletion na rozhraní a LINQ spoléhají na IEnumerable<T>.Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. Mechanismus doručování se ale mění, protože .NET se stane nezávisle na platformě.However, the delivery mechanism is changing as .NET is becoming more platform independent. .NET Framework nemusí vždy dodávat do stejného tempo jako kompilátor jazyka.The .NET Framework may not always ship on the same cadence as the language compiler. Když nové funkce jazyka spoléhají na nové typy, budou tyto typy k dispozici jako balíčky NuGet, když se funkce jazyka dodávají.When new language features rely on new types, those types will be available as NuGet packages when the language features ship. Jelikož se tyto nové typy přidají do rozhraní .NET Standard API a doručují se jako součást architektury, požadavek na balíček NuGet se odebere.As these new types get added to the .NET Standard API and delivered as part of the framework, the NuGet package requirement will be removed.

Pojďme začít s důvody pro přidání nové podpory řazené kolekce členů.Let's start with the reasons for adding new tuple support. Metody vrací jeden objekt.Methods return a single object. Řazené kolekce členů umožňují snadněji zabalit více hodnot v jednom objektu.Tuples enable you to package multiple values in that single object more easily.

.NET Framework již obsahuje obecné Tuple třídy.The .NET Framework already has generic Tuple classes. Tyto třídy však měly dvě hlavní omezení.These classes, however, had two major limitations. Pro jednu Tuple třídu s názvem jejich vlastnosti Item1, Item2a tak dále.For one, the Tuple classes named their properties Item1, Item2, and so on. Tyto názvy nenesou žádné sémantické informace.Those names carry no semantic information. Použití těchto Tuple typů neumožňuje komunikaci významu jednotlivých vlastností.Using these Tuple types does not enable communicating the meaning of each of the properties. Nové funkce jazyka umožňují deklarovat a používat sémanticky smysluplné názvy pro prvky v řazené kolekci členů.The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

Třídy Tuple způsobují větší vliv na výkon, protože se jedná o odkazové typy.The Tuple classes cause more performance concerns because they are reference types. Použití jednoho z Tuple typů znamená přidělení objektů.Using one of the Tuple types means allocating objects. V případě aktivních cest může mít přidělení mnoha malých objektů měřitelný dopad na výkon aplikace.On hot paths, allocating many small objects can have a measurable impact on your application's performance. Proto jazyková podpora pro řazené kolekce členů využívá nové ValueTuple struktury.Therefore, the language support for tuples leverages the new ValueTuple structs.

Chcete-li se těmto nedostatkům vyhnout, class můžete vytvořit struct nebo a převést více prvků.To avoid those deficiencies, you could create a class or a struct to carry multiple elements. Bohužel to více funguje za vás a zakrývá váš záměr návrhu.Unfortunately, that's more work for you, and it obscures your design intent. struct Vytvoření nebo class znamená, že definujete typ s daty a chováním.Making a struct or class implies that you are defining a type with both data and behavior. Mnohokrát stačí uložit více hodnot do jednoho objektu.Many times, you simply want to store multiple values in a single object.

Jazykové funkce a ValueTuple obecné struktury vynutily pravidlo, že nemůžete přidat žádné chování (metody) k těmto typům řazené kolekce členů.The language features and the ValueTuple generic structs enforce the rule that you cannot add any behavior (methods) to these tuple types. Všechny typy jsou proměnlivé struktury. ValueTupleAll the ValueTuple types are mutable structs. Každé pole člena je veřejné pole.Each member field is a public field. Díky tomu jsou velmi odlehčené.That makes them very lightweight. To ale znamená, že by se neměly používat řazené kolekce členů tam, kde je neměnnosti důležité.However, that means tuples should not be used where immutability is important.

Řazené kolekce členů jsou jednodušší a pružnější datové kontejnery class než struct a typy.Tuples are both simpler and more flexible data containers than class and struct types. Pojďme tyto rozdíly prozkoumat.Let's explore those differences.

Pojmenované a nepojmenované řazené kolekce členůNamed and unnamed tuples

Item1 Item2 Tuple Struktura obsahuje pole s názvy,, Item3a tak dále, podobně jako vlastnosti definované v existujících typech. ValueTupleThe ValueTuple struct has fields named Item1, Item2, Item3, and so on, similar to the properties defined in the existing Tuple types. Tyto názvy jsou jedinými názvy, které můžete použít pro nepojmenované řazené kolekce členů.These names are the only names you can use for unnamed tuples. Pokud neposkytnete žádné alternativní názvy polí do řazené kolekce členů, vytvořili jste nepojmenované řazené kolekce členů:When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

var unnamed = ("one", "two");

Řazená kolekce členů v předchozím příkladu byla inicializována pomocí literálových konstant a nebude mít názvy prvků vytvořené pomocí projekce názvů polí řazené kolekce členů v C# 7,1.The tuple in the previous example was initialized using literal constants and won't have element names created using tuple field name projections in C# 7.1.

Při inicializaci řazené kolekce členů ale můžete použít nové funkce jazyka, které každému poli přidávají lepší názvy.However, when you initialize a tuple, you can use new language features that give better names to each field. Tím dojde k vytvoření pojmenované řazené kolekce členů.Doing so creates a named tuple. Pojmenované řazené kolekce členů mají stále Item1elementy Item2s Item3 názvem, a tak dále.Named tuples still have elements named Item1, Item2, Item3 and so on. Mají však také synonyma pro libovolný z těchto elementů, které máte pojmenovány.But they also have synonyms for any of those elements that you have named. Pojmenovanou řazenou kolekci členů vytvoříte zadáním názvů pro každý prvek.You create a named tuple by specifying the names for each element. Jedním ze způsobů, jak zadat názvy v rámci inicializace řazené kolekce členů:One way is to specify the names as part of the tuple initialization:

var named = (first: "one", second: "two");

Tato synonyma jsou zpracovávána kompilátorem a jazykem, aby bylo možné používat pojmenované řazené kolekce členů efektivně.These synonyms are handled by the compiler and the language so that you can use named tuples effectively. IDEs a editory můžou tyto sémantické názvy číst pomocí rozhraní Roslyn API.IDEs and editors can read these semantic names using the Roslyn APIs. Na prvky pojmenované řazené kolekce členů můžete odkazovat pomocí těchto sémantických názvů kdekoli ve stejném sestavení.You can reference the elements of a named tuple by those semantic names anywhere in the same assembly. Kompilátor nahradí názvy, které jste definovali Item* , ekvivalenty při generování zkompilovaného výstupu.The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. Kompilovaný jazyk MSIL (Microsoft Intermediate Language) neobsahuje názvy, které jste těmto prvkům zadali.The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

Počínaje C# 7,1 se názvy polí pro řazené kolekce členů můžou poskytovat z proměnných používaných k inicializaci řazené kolekce členů.Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. Tato metoda je označována jako Inicializátory projekcí řazené kolekce členů .This is referred to as tuple projection initializers. Následující kód vytvoří řazenou kolekci členů accumulation s názvem count s prvky (celé číslo) sum a (Double).The following code creates a tuple named accumulation with elements count (an integer), and sum (a double).

var sum = 12.5;
var count = 5;
var accumulation = (count, sum);

Kompilátor musí komunikovat s názvy, které jste vytvořili pro řazené kolekce členů, které jsou vráceny z veřejných metod nebo vlastností.The compiler must communicate those names you created for tuples that are returned from public methods or properties. V těchto případech kompilátor přidá TupleElementNamesAttribute atribut metody.In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. Tento atribut obsahuje TransformNames vlastnost seznamu, která obsahuje názvy zadané pro každý prvek v řazené kolekci členů.This attribute contains a TransformNames list property that contains the names given to each of the elements in the tuple.

Poznámka

Nástroje pro vývoj, jako je třeba Visual Studio, čtou tato metadata a poskytují IntelliSense a další funkce pomocí názvů polí metadat.Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

Je důležité pochopit tyto základní základy nových řazených kolekcí členů a ValueTuple typ, aby bylo možné pochopit pravidla pro přiřazování pojmenovaných řazených kolekcí členů.It is important to understand these underlying fundamentals of the new tuples and the ValueTuple type in order to understand the rules for assigning named tuples to each other.

Inicializátory řazené kolekce členůTuple projection initializers

Obecně se Inicializátory řazené kolekce členů pracují pomocí názvů proměnných nebo polí z pravé strany příkazu inicializace řazené kolekce členů.In general, tuple projection initializers work by using the variable or field names from the right-hand side of a tuple initialization statement. Pokud je zadán explicitní název, který má přednost před libovolným názvem projektu.If an explicit name is given, that takes precedence over any projected name. Například v následujícím explicitFieldOne inicializátoru jsou prvky a explicitFieldTwo, nikoli localVariableOne a localVariableTwo:For example, in the following initializer, the elements are explicitFieldOne and explicitFieldTwo, not localVariableOne and localVariableTwo:

var localVariableOne = 5;
var localVariableTwo = "some text";

var tuple = (explicitFieldOne: localVariableOne, explicitFieldTwo: localVariableTwo);

Pro jakékoli pole, kde není zadáno explicitní jméno, je projekt použit s implicitním názvem.For any field where an explicit name is not provided, an applicable implicit name is projected. Neexistuje žádný požadavek na poskytování sémantických názvů buď explicitně, nebo implicitně.There is no requirement to provide semantic names, either explicitly or implicitly. Následující inicializátor má názvy Item1polí, jejichž hodnota je 42 a stringContent, jejíž hodnota je "odpověď na vše":The following initializer has field names Item1, whose value is 42 and stringContent, whose value is "The answer to everything":

var stringContent = "The answer to everything";
var mixedTuple = (42, stringContent);

Existují dvě podmínky, kdy názvy polí kandidátů nejsou promítnuty do pole řazené kolekce členů:There are two conditions where candidate field names are not projected onto the tuple field:

  1. Pokud je kandidátem název vyhrazený název řazené kolekce členů.When the candidate name is a reserved tuple name. Příklady zahrnují Item3, ToString.Examples include Item3, ToString. nebo Rest.or Rest.
  2. Pokud je název kandidáta duplicitní s jiným názvem pole řazené kolekce členů, buď explicitní, nebo implicitní.When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

Tyto podmínky zabraňují nejednoznačnosti.These conditions avoid ambiguity. Tyto názvy by způsobily nejednoznačnost, pokud byly použity jako názvy polí v řazené kolekci členů.These names would cause an ambiguity if they were used as the field names for a field in a tuple. Ani jedna z těchto podmínek nezpůsobí chyby při kompilaci.Neither of these conditions cause compile-time errors. Místo toho pro prvky bez projektových názvů nejsou pro ně sémanticky pojmenovány.Instead, the elements without projected names do not have semantic names projected for them. Následující příklady znázorňují tyto podmínky:The following examples demonstrate these conditions:

var ToString = "This is some text";
var one = 1;
var Item1 = 5;
var projections = (ToString, one, Item1);
// Accessing the first field:
Console.WriteLine(projections.Item1);
// There is no semantic name 'ToString'
// Accessing the second field:
Console.WriteLine(projections.one);
Console.WriteLine(projections.Item2);
// Accessing the third field:
Console.WriteLine(projections.Item3);
// There is no semantic name 'Item1`.

var pt1 = (X: 3, Y: 0);
var pt2 = (X: 3, Y: 4);

var xCoords = (pt1.X, pt2.X);
// There are no semantic names for the fields
// of xCoords. 

// Accessing the first field:
Console.WriteLine(xCoords.Item1);
// Accessing the second field:
Console.WriteLine(xCoords.Item2);

Tyto situace nezpůsobují chyby kompilátoru, protože by se jednalo o zásadní změnu kódu napsaného pomocí C# 7,0, pokud nedošlo k dispozici pro název pole řazené kolekce členů.These situations do not cause compiler errors because that would be a breaking change for code written with C# 7.0, when tuple field name projections were not available.

Rovnost a řazené kolekce členůEquality and tuples

Počínaje C# 7,3, typy řazené kolekce členů == podporují != operátory a.Beginning with C# 7.3, tuple types support the == and != operators. Tyto operátory pracují pomocí porovnání jednotlivých členů levého argumentu s každým členem pravého argumentu v daném pořadí.These operators work by comparing each member of the left argument to each member of the right argument in order. Tyto porovnávací krátkodobé okruhy.These comparisons short-circuit. Přestanou vyhodnocovat členy, jakmile se jeden pár nerovná.They will stop evaluating members as soon as one pair is not equal. Následující příklady kódu používají ==, ale pravidla porovnání platí pro. !=The following code examples use ==, but the comparison rules all apply to !=. Následující příklad kódu ukazuje porovnání rovnosti pro dvě páry celých čísel:The following code example shows an equality comparison for two pairs of integers:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // displays 'true'

Existuje několik pravidel, která umožňují pohodlnější testy rovnosti řazené kolekce členů.There are several rules that make tuple equality tests more convenient. Rovnost řazené kolekce členů provádí převedené převody , pokud je jedna z řazených kolekcí členů s možnou hodnotou null, jak je znázorněno v následujícím kódu:Tuple equality performs lifted conversions if one of the tuples is a nullable tuple, as shown in the following code:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
(int a, int b)? nullableTuple = right;
Console.WriteLine(left == nullableTuple); // Also true

Rovnost řazené kolekce členů také provádí implicitní převody na každého členu obou řazených kolekcí členů.Tuple equality also performs implicit conversions on each member of both tuples. Patří mezi ně zrušené převody, rozšiřující převody nebo jiné implicitní převody.These include lifted conversions, widening conversions, or other implicit conversions. Následující příklady ukazují, že celočíselná hodnota 2 – řazená kolekce členů může být porovnána s dlouhou meziřazenou kolekcí členů z důvodu implicitního převodu z celého čísla na Long:The following examples show that an integer 2-tuple can be compared to a long 2-tuple because of the implicit conversion from integer to long:

// lifted conversions
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true

// converted type of left is (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Also true

// comparisons performed on (long, long) tuples
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Also true

Názvy členů řazené kolekce členů se neúčastní testů pro rovnost.The names of the tuple members do not participate in tests for equality. Pokud je však jeden z operandů literál řazené kolekce členů s explicitními názvy, kompilátor vygeneruje upozornění CS8383, pokud tyto názvy neodpovídají názvům jiného operandu.However, if one of the operands is a tuple literal with explicit names, the compiler generates warning CS8383 if those names do not match the names of the other operand. V případě, kdy jsou oba operandy literály řazené kolekce členů, je upozornění na pravém operandu, jak je znázorněno v následujícím příkladu:In the case where both operands are tuple literals, the warning is on the right operand as shown in the following example:

(int a, string b) pair = (1, "Hello");
(int z, string y) another = (1, "Hello");
Console.WriteLine(pair == another); // true. Member names don't participate.
Console.WriteLine(pair == (z: 1, y: "Hello")); // warning: literal contains different member names

A konečně, řazené kolekce členů můžou obsahovat vnořené řazené kolekce členů.Finally, tuples may contain nested tuples. Rovnost řazené kolekce členů porovnává "tvar" každého operandu prostřednictvím vnořených řazených kolekcí členů, jak je znázorněno v následujícím příkladu:Tuple equality compares the "shape" of each operand through nested tuples as shown in the following example:

(int, (int, int)) nestedTuple = (1, (2, 3));
Console.WriteLine(nestedTuple == (1, (2, 3)) );

Jedná se o chybu při kompilaci pro porovnání dvou řazených kolekcí členů s rovností (nebo nerovnosti), pokud mají různé tvary.It's a compile time error to compare two tuples for equality (or inequality) when they have different shapes. Kompilátor se nebude pokoušet o žádné dekonstrukci vnořených řazených kolekcí členů, aby je bylo možné porovnat.The compiler won't attempt any deconstruction of nested tuples in order to compare them.

Přiřazení a řazené kolekce členůAssignment and tuples

Jazyk podporuje přiřazení mezi typy řazené kolekce členů, které mají stejný počet prvků, kde každý prvek na pravé straně lze implicitně převést na odpovídající element na levé straně.The language supports assignment between tuple types that have the same number of elements, where each right-hand side element can be implicitly converted to its corresponding left-hand side element. Jiné převody nejsou považovány za přiřazení.Other conversions aren't considered for assignments. Jedná se o chybu při kompilaci k přiřazení jedné řazené kolekce členů k druhému, pokud mají různé tvary.It's a compile time error to assign one tuple to another when they have different shapes. Kompilátor se nebude pokoušet o žádné dekonstrukci vnořených řazených kolekcí členů, aby je bylo možné přiřadit.The compiler won't attempt any deconstruction of nested tuples in order to assign them. Pojďme se podívat na typy přiřazení, která jsou povolená mezi typy řazené kolekce členů.Let's look at the kinds of assignments that are allowed between tuple types.

Vezměte v úvahu tyto proměnné, které se používají v následujících příkladech:Consider these variables used in the following examples:

// The 'arity' and 'shape' of all these tuples are compatible. 
// The only difference is the field names being used.
var unnamed = (42, "The meaning of life");
var anonymous = (16, "a perfect square");
var named = (Answer: 42, Message: "The meaning of life");
var differentNamed = (SecretConstant: 42, Label: "The meaning of life");

První dvě proměnné unnamed a anonymous nemají pro prvky k dispozici sémantické názvy.The first two variables, unnamed and anonymous do not have semantic names provided for the elements. Názvy polí jsou Item1 a Item2.The field names are Item1 and Item2. Poslední dvě proměnné named a differentName mají pro prvky zadány sémantické názvy.The last two variables, named and differentName have semantic names given for the elements. Tyto dvě řazené kolekce členů mají různé názvy pro prvky.These two tuples have different names for the elements.

Všechny čtyři tyto řazené kolekce členů mají stejný počet elementů (označovaných jako mohutnost) a typy těchto elementů jsou identické.All four of these tuples have the same number of elements (referred to as 'cardinality') and the types of those elements are identical. Proto všechna tato přiřazení fungují:Therefore, all of these assignments work:

unnamed = named;

named = unnamed;
// 'named' still has fields that can be referred to
// as 'answer', and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// unnamed to unnamed:
anonymous = unnamed;

// named tuples.
named = differentNamed;
// The field names are not assigned. 'named' still has 
// fields that can be referred to as 'answer' and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");

// With implicit conversions:
// int can be implicitly converted to long
(long, string) conversion = named;

Všimněte si, že názvy řazených kolekcí členů nejsou přiřazeny.Notice that the names of the tuples are not assigned. Hodnoty prvků jsou přiřazeny podle pořadí prvků v řazené kolekci členů.The values of the elements are assigned following the order of the elements in the tuple.

Řazené kolekce členů různých typů nebo čísel prvků nelze přiřadit:Tuples of different types or numbers of elements are not assignable:

// Does not compile.
// CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string)
var differentShape = (1, 2, 3);
named = differentShape;

Řazené kolekce členů jako návratové hodnoty metodyTuples as method return values

Jedním z nejběžnějších použití pro řazené kolekce členů je jako návratová hodnota metody.One of the most common uses for tuples is as a method return value. Pojďme si projít jeden příklad.Let's walk through one example. Zvažte tuto metodu, která vypočítá směrodatnou odchylku pro posloupnost čísel:Consider this method that computes the standard deviation for a sequence of numbers:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    // Step 1: Compute the Mean:
    var mean = sequence.Average();

    // Step 2: Compute the square of the differences between each number 
    // and the mean:
    var squaredMeanDifferences = from n in sequence
                                 select (n - mean) * (n - mean);
    // Step 3: Find the mean of those squared differences:
    var meanOfSquaredDifferences = squaredMeanDifferences.Average();

    // Step 4: Standard Deviation is the square root of that mean:
    var standardDeviation = Math.Sqrt(meanOfSquaredDifferences);
    return standardDeviation;
}

Poznámka

Tyto příklady vypočítají nesprávnou směrodatnou odchylku vzorků.These examples compute the uncorrected sample standard deviation. Opravený Vzorec směrodatné odchylky (standardní vzorek) rozdělí součet kvadratických rozdílů od střední hodnoty (N-1) namísto N, jako Average metoda rozšíření.The corrected sample standard deviation formula would divide the sum of the squared differences from the mean by (N-1) instead of N, as the Average extension method does. Pokud chcete získat další informace o rozdílech mezi těmito vzorci pro směrodatnou odchylku, Projděte si text statistiky.Consult a statistics text for more details on the differences between these formulas for standard deviation.

Předchozí kód následuje vzorec Textbook pro směrodatnou odchylku.The preceding code follows the textbook formula for the standard deviation. Vytváří správnou odpověď, ale jedná se o neefektivní implementaci.It produces the correct answer, but it's an inefficient implementation. Tato metoda vytvoří výčet pořadí dvakrát: Jakmile vypočítáte průměr a jednou, získáte průměrnou mocninu rozdílu průměru.This method enumerates the sequence twice: Once to produce the average, and once to produce the average of the square of the difference of the average. (Nezapomeňte, že dotazy LINQ jsou vyhodnoceny jako laxně vytvářená, takže výpočet rozdílů od střední hodnoty a průměr těchto rozdílů činí pouze jeden výčet.)(Remember that LINQ queries are evaluated lazily, so the computation of the differences from the mean and the average of those differences makes only one enumeration.)

Existuje alternativní vzorec, který počítá směrodatnou odchylku pomocí pouze jednoho výčtu sekvence.There is an alternative formula that computes standard deviation using only one enumeration of the sequence. Tento výpočet vytvoří dvě hodnoty, protože sestaví sekvenci: součet všech položek v sekvenci a součet každé hodnoty čtverce:This computation produces two values as it enumerates the sequence: the sum of all items in the sequence, and the sum of the each value squared:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    double count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Tato verze sestaví sekvenci přesně jednou.This version enumerates the sequence exactly once. Nejedná se ale o opakovaně použitelný kód.But it's not reusable code. Jak budete pracovat, zjistíte, že mnoho různých statistických výpočtů používá počet položek v sekvenci, součet sekvence a součet čtverců sekvence.As you keep working, you'll find that many different statistical computations use the number of items in the sequence, the sum of the sequence, and the sum of the squares of the sequence. Pojďme tuto metodu Refaktorovat a napsat metodu nástroje, která vytvoří všechny tři tyto hodnoty.Let's refactor this method and write a utility method that produces all three of those values. Všechny tři hodnoty mohou být vráceny jako řazené kolekce členů.All three values can be returned as a tuple.

Pojďme tuto metodu aktualizovat tak, aby tři hodnoty vypočítané během výčtu byly uloženy v řazené kolekci členů.Let's update this method so the three values computed during the enumeration are stored in a tuple. Vytváří tuto verzi:That creates this version:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = (Count: 0, Sum: 0.0, SumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.Count++;
        computation.Sum += item;
        computation.SumOfSquares += item * item;
    }

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

Podpora refaktoringu sady Visual Studio usnadňuje extrakci funkcí základních statistik do privátní metody.Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. Poskytuje private static metodu, která vrací typ řazené kolekce členů se třemi Sumhodnotami, SumOfSquaresa Count:That gives you a private static method that returns the tuple type with the three values of Sum, SumOfSquares, and Count:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int Count, double Sum, double SumOfSquares) computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    var computation = (count: 0, sum: 0.0, sumOfSquares: 0.0);

    foreach (var item in sequence)
    {
        computation.count++;
        computation.sum += item;
        computation.sumOfSquares += item * item;
    }

    return computation;
}

Jazyk umožňuje několik dalších možností, které můžete použít, pokud chcete udělat několik rychlých úprav ručně.The language enables a couple more options that you can use, if you want to make a few quick edits by hand. Nejprve můžete použít var deklaraci k inicializaci výsledku řazené kolekce členů ComputeSumAndSumOfSquares z volání metody.First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. V ComputeSumAndSumOfSquares rámci metody můžete také vytvořit tři diskrétní proměnné.You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. Finální verze je zobrazená v následujícím kódu:The final version is shown in the following code:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var computation = ComputeSumAndSumOfSquares(sequence);

    var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
    return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (count, sum, sumOfSquares);
}

Tato konečná verze se dá použít pro libovolnou metodu, která potřebuje tyto tři hodnoty, nebo jakoukoli její podmnožinu.This final version can be used for any method that needs those three values, or any subset of them.

Jazyk podporuje další možnosti správy názvů prvků v těchto metodách vracejících řazené kolekce členů.The language supports other options in managing the names of the elements in these tuple-returning methods.

Můžete odebrat názvy polí z deklarace návratové hodnoty a vrátit nepojmenované řazené kolekce členů:You can remove the field names from the return value declaration and return an unnamed tuple:

private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
    double sum = 0;
    double sumOfSquares = 0;
    int count = 0;

    foreach (var item in sequence)
    {
        count++;
        sum += item;
        sumOfSquares += item * item;
    }

    return (sum, sumOfSquares, count);
}

Pole této řazené kolekce členů jsou Item1pojmenovány Item3, Item2a.The fields of this tuple are named Item1, Item2, and Item3. Doporučuje se poskytnout sémantické názvy prvků řazených kolekcí členů vrácených z metod.It's recommended that you provide semantic names to the elements of tuples returned from methods.

Další idiom v případě, že řazené kolekce členů mohou být užitečné, je při vytváření dotazů LINQ.Another idiom where tuples can be useful is when you are authoring LINQ queries. Konečný plánovaný výsledek často obsahuje některé z vlastností objektů, které jsou vybrány, ale ne všechny.The final projected result often contains some, but not all, of the properties of the objects being selected.

Výsledky dotazu byste měli zamítnout do posloupnosti objektů, které byly anonymního typu.You would traditionally project the results of the query into a sequence of objects that were an anonymous type. , Který prezentuje mnoho omezení, hlavně proto, že anonymní typy nelze pohodlně pojmenovat v návratovém typu pro metodu.That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. Alternativy použití object nebo dynamic jako typ výsledku byly v důsledku značných nákladů na výkon.Alternatives using object or dynamic as the type of the result came with significant performance costs.

Vrácení sekvence typu řazené kolekce členů je jednoduché a názvy a typy prvků jsou k dispozici v době kompilace a prostřednictvím nástrojů IDE.Returning a sequence of a tuple type is easy, and the names and types of the elements are available at compile time and through IDE tools. Zvažte například aplikaci ToDo.For example, consider a ToDo application. Můžete definovat třídu podobnou následující, aby představovala jedinou položku v seznamu ToDo:You might define a class similar to the following to represent a single entry in the ToDo list:

public class ToDoItem
{
    public int ID { get; set; }
    public bool IsDone { get; set; }
    public DateTime DueDate { get; set; }
    public string Title { get; set; }
    public string Notes { get; set; }    
}

Vaše mobilní aplikace mohou podporovat kompaktní formu aktuálních položek ToDo, které zobrazují pouze nadpis.Your mobile applications may support a compact form of the current ToDo items that only displays the title. Tento dotaz LINQ by provedl projekci, která obsahuje pouze ID a název.That LINQ query would make a projection that includes only the ID and the title. Metoda, která vrací sekvenci řazených kolekcí členů vyjadřuje tento návrh dobře:A method that returns a sequence of tuples expresses that design well:

internal IEnumerable<(int ID, string Title)> GetCurrentItemsMobileList()
{
    return from item in AllItems
           where !item.IsDone
           orderby item.DueDate
           select (item.ID, item.Title);
}

Poznámka

V C# 7,1 řazené kolekce členů umožňují vytvořit pojmenované řazené kolekce členů pomocí prvků podobným způsobem jako pojmenovávání vlastností v anonymních typech.In C# 7.1, tuple projections enable you to create named tuples using elements, in a manner similar to the property naming in anonymous types. Ve výše uvedeném kódu select příkaz v projekci dotazu vytvoří řazenou kolekci členů, která obsahuje prvky ID a. TitleIn the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

Pojmenovaná řazená kolekce členů může být součástí signatury.The named tuple can be part of the signature. Umožňuje kompilátoru a nástrojům IDE poskytovat statickou kontrolu, že výsledek používáte správně.It lets the compiler and IDE tools provide static checking that you are using the result correctly. Pojmenované řazené kolekce členů také přenáší informace o statickém typu, takže není nutné používat nákladné funkce běhu jako reflexe nebo dynamická vazba pro práci s výsledky.The named tuple also carries the static type information so there is no need to use expensive run time features like reflection or dynamic binding to work with the results.

DekonstrukceDeconstruction

Můžete odbalit všechny položky v řazené kolekci členů tím, že dekonstruujete řazenou kolekci členů vrácenou metodou.You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. Existují tři různé přístupy k dekonstrukci řazených kolekcí členů.There are three different approaches to deconstructing tuples. Nejprve můžete explicitně deklarovat typ každého pole uvnitř závorek a vytvořit tak diskrétní proměnné pro každý prvek řazené kolekce členů:First, you can explicitly declare the type of each field inside parentheses to create discrete variables for each of the elements in the tuple:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    (int count, double sum, double sumOfSquares) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Můžete také deklarovat implicitně typové proměnné pro každé pole v řazené kolekci členů pomocí var klíčového slova vně závorek:You can also declare implicitly typed variables for each field in a tuple by using the var keyword outside the parentheses:

public static double StandardDeviation(IEnumerable<double> sequence)
{
    var (sum, sumOfSquares, count) = ComputeSumAndSumOfSquares(sequence);

    var variance = sumOfSquares - sum * sum / count;
    return Math.Sqrt(variance / count);
}

Také je právní použití var klíčového slova s libovolnou nebo všemi deklaracemi proměnných uvnitř závorek.It is also legal to use the var keyword with any, or all of the variable declarations inside the parentheses.

(double sum, var sumOfSquares, var count) = ComputeSumAndSumOfSquares(sequence);

Nemůžete použít konkrétní typ mimo závorky, a to ani v případě, že každé pole v řazené kolekci členů má stejný typ.You cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

Můžete dekonstruovat řazené kolekce členů i s existujícími deklaracemi:You can deconstruct tuples with existing declarations as well:

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

    public Point(int x, int y) => (X, Y) = (x, y);
}

Varování

V závorkách nelze kombinovat existující deklarace s deklaracemi.You cannot mix existing declarations with declarations inside the parentheses. Následující není například povoleno: (var x, y) = MyMethod();.For instance, the following is not allowed: (var x, y) = MyMethod();. Tím se vytvoří chyba CS8184, protože x je deklarované uvnitř závorek a y je dřív deklarovaný jinde.This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

Dekonstrukce uživatelem definovaných typůDeconstructing user-defined types

Libovolný typ řazené kolekce členů lze dekonstruovat, jak je uvedeno výše.Any tuple type can be deconstructed as shown above. Je také snadné povolit dekonstrukci u libovolného uživatelsky definovaného typu (třídy, struktury nebo dokonce rozhraní).It's also easy to enable deconstruction on any user-defined type (classes, structs, or even interfaces).

Autor typu může definovat jednu nebo více Deconstruct metod, které přiřazují hodnoty k libovolnému out počtu proměnných reprezentujícím datové prvky, které tvoří typ.The type author can define one or more Deconstruct methods that assign values to any number of out variables representing the data elements that make up the type. Například následující Person typ Deconstruct definuje metodu, která dekonstruuje objekt Person do prvků představujících křestní jméno a příjmení:For example, the following Person type defines a Deconstruct method that deconstructs a person object into the elements representing the first name and last name:

public class Person
{
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }

    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}

Metoda dekonstrukce umožňuje přiřazení z Person a do dvou řetězců, které FirstName představují vlastnosti a LastName :The deconstruct method enables assignment from a Person to two strings, representing the FirstName and LastName properties:

var p = new Person("Althea", "Goodwin");
var (first, last) = p;

Můžete povolit dekonstrukci i pro typy, které jste nevytvořili.You can enable deconstruction even for types you did not author. Deconstruct Metoda může být metoda rozšíření, která odbalí přístupné datové členy objektu.The Deconstruct method can be an extension method that unpackages the accessible data members of an object. Níže uvedený Student příklad ukazuje typ, odvozený Person z typu, a metodu rozšíření Student , která dekonstruujee LastNamedo tří proměnných GPA, reprezentující FirstName, a:The example below shows a Student type, derived from the Person type, and an extension method that deconstructs a Student into three variables, representing the FirstName, the LastName, and the GPA:

public class Student : Person
{
    public double GPA { get; }
    public Student(string first, string last, double gpa) :
        base(first, last)
    {
        GPA = gpa;
    }
}

public static class Extensions
{
    public static void Deconstruct(this Student s, out string first, out string last, out double gpa)
    {
        first = s.FirstName;
        last = s.LastName;
        gpa = s.GPA;
    }
}

Objekt má nyní dvě dostupné Deconstruct metody: metodu rozšíření deklarovanou pro Student typy a člena Person typu. StudentA Student object now has two accessible Deconstruct methods: the extension method declared for Student types, and the member of the Person type. Obě jsou v oboru, což umožňuje Student , aby bylo možné ho rozdělit do dvou proměnných nebo tří.Both are in scope, and that enables a Student to be deconstructed into either two variables or three. Pokud každému studentovi přiřadíte tři proměnné, vrátí se všechna jeho křestní jméno, příjmení a GPA.If you assign a student to three variables, the first name, last name, and GPA are all returned. Pokud do dvou proměnných přiřadíte studenta, vrátí se pouze jméno a příjmení.If you assign a student to two variables, only the first name and the last name are returned.

var s1 = new Student("Cary", "Totten", 4.5);
var (fName, lName, gpa) = s1;

Měli byste pečlivě definovat více Deconstruct metod ve třídě nebo hierarchii tříd.You should be careful defining multiple Deconstruct methods in a class or a class hierarchy. Více Deconstruct metod, které mají stejný out počet parametrů, může rychle způsobit nejednoznačnosti.Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. Volající nemusí být schopni snadno volat požadovanou Deconstruct metodu.Callers may not be able to easily call the desired Deconstruct method.

V tomto příkladu je minimální Deconstruct pravděpodobnost pro dvojznačné volání, protože metoda pro Person má dva Deconstruct výstupní parametry a metoda pro Student má tři.In this example, there is minimal chance for an ambiguous call because the Deconstruct method for Person has two output parameters, and the Deconstruct method for Student has three.

Operátory dekonstrukce se nepodílejí na testování rovnosti.Deconstruction operators do not participate in testing equality. Následující příklad generuje chybu kompilátoru CS0019:The following example generates compiler error CS0019:

Person p = new Person("Althea", "Goodwin");
if (("Althea", "Goodwin") == p)
    Console.WriteLine(p);

Metoda by mohla Person převést objekt p na řazenou kolekci členů, která obsahuje dva řetězce, ale není platná v kontextu testů rovnosti. DeconstructThe Deconstruct method could convert the Person object p to a tuple containing two strings, but it is not applicable in the context of equality tests.

ZávěrConclusion

Podpora nového jazyka a knihovny pro pojmenované řazené kolekce členů usnadňuje práci s návrhy, které používají datové struktury, které ukládají více elementů, ale nedefinují chování, jako třídy a struktury.The new language and library support for named tuples makes it much easier to work with designs that use data structures that store multiple elements but do not define behavior, as classes and structs do. Použití řazených kolekcí členů pro tyto typy je jednoduché a stručné.It's easy and concise to use tuples for those types. Získáte všechny výhody kontroly statického typu, aniž byste museli vytvářet typy pomocí podrobnějšího class příkazu nebo struct syntaxe.You get all the benefits of static type checking, without needing to author types using the more verbose class or struct syntax. I tak je nejužitečnější pro obslužné metody, které jsou privatenebo. internalEven so, they are most useful for utility methods that are private, or internal. Vytvořte uživatelsky definované typy, buď class nebo struct typy, pokud vaše veřejné metody vrací hodnotu, která má více prvků.Create user-defined types, either class or struct types when your public methods return a value that has multiple elements.