diff --git a/docs/csharp/csharp-7.md b/docs/csharp/csharp-7.md index d9f61a19de91e..51031edb61697 100644 --- a/docs/csharp/csharp-7.md +++ b/docs/csharp/csharp-7.md @@ -57,7 +57,6 @@ Add a sample at RC that shows how if statements scope out variables. --> -This small language change improves your productivity in important ways: * The code is easier to read. - You declare the out variable where you use it, not on another line above. * No need to assign an initial value. @@ -78,9 +77,10 @@ that contain multiple fields to represent the data members. The fields are not validated, and you cannot define your own methods > [!NOTE] -> Tuples were available as an API class before C# 7, but had many limitations. -> Most importantly, the members of these tuples were named `Item1`, `Item2` -> and so on. The language features for Tuples address this limitations. +> Tuples were available before C# 7 as an API, but had many limitations. +> Most importantly, the members of these tuples were named +> `Item1`, `Item2` and so on. The language support enables semantic names +> for the fields of a Tuple. You can create a tuple by assigning each member to a value: @@ -94,12 +94,12 @@ names to each of the members of the tuple: [!code-csharp[NamedTuple](../../samples/snippets/csharp/new-in-7/new-in-7/program.cs#05_NamedTuple "Named tuple")] > [!NOTE] -> The new tuples features require the @System.ValueTuple type. For Visual Studio 15 +> The new tuples features require the `System.ValueTuple` type. For Visual Studio 15 > Preview 5 and earlier preview releases, you must add the NuGet package "System.ValueTuple", > available in the pre-release stream. -The `namedLetters` tuple contains fields referred to as `alpha` and -`beta`. In a tuple assignment, you can also specify the names of the fields +The `namedLetters` tuple contains fields referred to as `Alpha` and +`Beta`. In a tuple assignment, you can also specify the names of the fields on the right hand side of the assignment: [!code-csharp[ImplicitNamedTuple](../../samples/snippets/csharp/new-in-7/new-in-7/program.cs#06_ImplicitNamedTuple "Implicitly named tuple")] @@ -110,24 +110,31 @@ left and right hand side of the assignment: [!code-csharp[NamedTupleConflict](../../samples/snippets/csharp/new-in-7/new-in-7/program.cs#07_NamedTupleConflict "Named tuple conflict")] The line above generates a warning, `CS8123`, telling you that the names on the right -side of the assignment, `alpha` and `beta` are ignored because they conflict -with the names on the left side, `first` and `second`. +side of the assignment, `Alpha` and `Beta` are ignored because they conflict +with the names on the left side, `First` and `Second`. The examples above show the basic syntax to declare tuples. Tuples are most useful as return types for `private` and `internal` methods. Tuples provide a simple syntax for those methods to return multiple discrete values: +You save the work of authoring a `class` or a `struct` that +defines the type returned. There is no need for creating a new type. + +Creating a tuple is more efficient and more productive. +It is a simpler, lightweight syntax to define a data structure that carries +more than one value. The example method below returns the minimimum and maximum +values found in a sequence of integers: [!code-csharp[TupleReturningMethod](../../samples/snippets/csharp/new-in-7/new-in-7/program.cs#08_TupleReturningMethod "Tuple returning method")] Using tuples in this way offers several advantages: * You save the work of authoring a `class` or a `struct` that defines the type returned. -* You do not need to create new symbol. +* You do not need to create new type. * The language enhancements removes the need to call the @System.Tuple.Create%60%601(%60%600) methods. The declaration for the method provides the names for the fields of the tuple that is returned. When you call the method, the return value is a -tuple whose fields are `max` and `min`: +tuple whose fields are `Max` and `Min`: [!code-csharp[CallingTupleMethod](../../samples/snippets/csharp/new-in-7/new-in-7/program.cs#09_CallingTupleMethod "Calling a tuple returning method")] @@ -337,7 +344,7 @@ the return value is a reference: Now, the second `WriteLine` statement in the example above will print out the value `24`, indicating that the storage in the matrix has been modified. The local variable has been declared with the `ref` modifier, -and it will take a `ref` return. You must initialize a `ref` varaible when +and it will take a `ref` return. You must initialize a `ref` variable when it is declared, you cannot split the declaration and the initialization. The C# language has two other rules that protect you from mis-using diff --git a/docs/csharp/tuples.md b/docs/csharp/tuples.md index 399e5482c076d..17e165c986024 100644 --- a/docs/csharp/tuples.md +++ b/docs/csharp/tuples.md @@ -1,10 +1,11 @@ --- title: Tuples | C# Guide -description: Learn about the unnamed tuple types in C# +description: Learn about unnamed and named tuple types in C# keywords: .NET, .NET Core, C# -author: dotnet-bot +author: BillWagner +ms-author: wiwagn manager: wpickett -ms.date: 10/04/2016 +ms.date: 11/23/2016 ms.topic: article ms.prod: .net-core ms.technology: .net-core-technologies @@ -12,30 +13,335 @@ ms.devlang: csharp ms.assetid: ee8bf7c3-aa3e-4c9e-a5c6-e05cc6138baa --- -# 🔧 Tuples +# C# Tuple types # -> **Note** -> -> This topic hasn’t been written yet! -> -> We welcome your input to help shape the scope and approach. You can track the status and provide input on this -> [issue](https://github.com/dotnet/docs/issues/1113) at GitHub. -> -> If you would like to review early drafts and outlines of this topic, please leave a note with your contact information in the issue. -> -> Learn more about how you can contribute on [GitHub](https://github.com/dotnet/docs/blob/master/CONTRIBUTING.md). -> +C# Tuples are types that you define using a lightweight syntax. The advantages +include a simpler syntax, rules for conversions based on number (referred to as "arity") +and types of fields, and +consistent rules for copies and assignments. As a tradeoff, Tuples do not +support some of the object oriented idioms associated with inheritance. You +can get an overview in the section on [Tuples in the What's new in C# 7](csharp-7.md#tuples) topic. - +The language features and the `ValueTuple` generic structs enforce the rule that +you cannot add any behavior (methods) to these tuple types. +All the `ValueTuple` types are *mutable structs*. Each member field is a +public field. That makes them very lightweight. However, that means tuples +should not be used where immutability is important. +Tuples are both simpler and more flexible data containers than `class` and +`struct` types. Let's explore those differences. + +## Named and unnamed tuples + +The `ValueTuple` struct has fields named `Item1`, `Item2`, `Item3` and so on, +similer to the properties defined in the existing `Tuple` types. +These names are the only names you can use for *unnamed tuples*. When you +do not provide any alternative field names to a tuple, you've created an +unnamed tuple: + +[!code-csharp[UnnamedTuple](../../samples/snippets/csharp/tuples/tuples/program.cs#01_UnNamedTuple "Unnamed tuple")] + +However, when you initialize a tuple, you can use new language features +that give better names to each field. Doing so creates a *named tuple*. +Named tuples still have fields named `Item1`, `Item2`, `Item3` and so on. +But they also have synonyms for any of those fields that you have named. +You create a named tuple by specifying the names for each field. One way +is to specify the names as part of the tuple initialization: + +[!code-csharp[NamedTuple](../../samples/snippets/csharp/tuples/tuples/program.cs#02_NamedTuple "Named tuple")] + +These synonyms are handled by the compiler and the language so that you +can use named tuples effectively. IDEs and editors can read these semantic names +using the Roslyn APIs. This enables you to reference the fields of a named +tuple by those semantic names anywhere in the same assembly. The compiler +replaces the names you've defined with `Item*` equivalents when generating +the compiled output. The compiled Microsoft Intermediate Language (MSIL) +does not include the names you've given these fields. + +The compiler must communicate those names you created for tuples that +are returned from public methods or properties. In those cases, the compiler +adds a `TupleElementNames` attribute on the method. This attribute contains +a `TransformNames` list property that contains the names given to each of +the fields in the Tuple. + +> [!NOTE] +> Development Tools, such as Visual Studio, also read that metadata, +> and provide intellisense and other features using the metadata +> field names. + +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. + +## Assignment and tuples + +The language supports assignment between tuple types that have +the same number of fields and the same types for each of those +fields. Those types must be exact compile-time matches. Other +conversions are not considered for assignments. Let's look at the kinds +of assignments that are allowed between tuple types. + +Consider these variables used in the following examples: + +[!code-csharp[VariableCreation](../../samples/snippets/csharp/tuples/tuples/program.cs#03_VariableCreation "Variable creation")] + +The first two variables, `unnamed` and `anonymous` do not have semantic +names provided for the fields. The field names are `Item1` and `Item2`. +The last two variables, `named` and `differentName` have semantic names +given for the fields. Note that these two tuples have different names +for the fields. + +All four of these tuples have the same number of fields (referred to as 'arity') +and the types of those fields are identical. Therefore, all of these +assignments work: + +[!code-csharp[VariableAssignment](../../samples/snippets/csharp/tuples/tuples/program.cs#04_VariableAssignment "Variable assignment")] + +Notice that the names of the tuples are not assigned. The values of the +fields are assigned following the order of the fields in the tuple. + +Tuples of different types or numbers of fields are not assignable: + +```csharp +// Does not compile. +// CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string) +var differentShape = (1, 2, 3); +named = differentShape; +``` + +## Tuples as method return values + +One of the most common uses for Tuples is as a method return +value. Let's walk through one example. Consider this method +that computes the standard deviation for a sequence of numbers: + +[!code-csharp[StandardDeviation](../../samples/snippets/csharp/tuples/tuples/statistics.cs#05_StandardDeviation "Compute Standard Deviation")] + +> [!NOTE] +> These examples compute the uncorrected sample standard deviation. +> 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. Consult a statistics +> text for more details on the differences between these formulas +> for standard deviation. + +This follows the textbook formula for the standard deviation. It produces +the correct answer, but it's a very inefficient implementation. 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. +(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.) + +There is an alternative formula that computes standard deviation using +only one enumeration of the sequence. 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: + +[!code-csharp[SumOfSquaresFormula](../../samples/snippets/csharp/tuples/tuples/statistics.cs#06_SumOfSquaresFormula "Compute Standard Deviation using the sum of squares")] + +Ths version enumerates the sequence exactly once. But, it's not very +reusable code. 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. Let's refactor this method and write +a utility method that produces all three of those values. + +This is where tuples come in very useful. + +Let's update this method so the three values computed during the enumeration +are stored in a tuple. That creates this version: + +[!code-csharp[TupleVersion](../../samples/snippets/csharp/tuples/tuples/statistics.cs#07_TupleVersion "Refactor to use tuples")] + +Visual Studio's Refactoring suport makes it easy to extract the functionality +for the core statistics into a private method. That gives you a `private static` +method that returns the tuple type with the three values of `Sum`, `SumOfSquares`, and `Count`: + +[!code-csharp[TupleMethodVersion](../../samples/snippets/csharp/tuples/tuples/statistics.cs#08_TupleMethodVersion "After extracting utility method")] + +The language enables a couple more options that you can use, if you want +to make a few quick edits by hand. First, you can use the `var` +declaration to initialize the tuple result from the `ComputeSumAndSumOfSquares` +method call. You can also create three discrete variables inside the +`ComputeSumAndSumOfSquares` method. The final version is below: + +[!code-csharp[CleanedTupleVersion](../../samples/snippets/csharp/tuples/tuples/statistics.cs#09_CleanedTupleVersion "After final cleanup")] + +This final version can be used for any method that needs those three +values, or any subset of them. + +The language supports other options in managing the names of the fields +in these tuple-returning methods. + +You can remove the field names from the return value declaration and +return an unnamed tuple: + +```csharp +private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable 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); +} +``` + +You must address the fields of this tuple as `Item1`, `Item2`, and `Item3`. +It's recommended that you provide semantic names to the fields of tuples +returned from methods. + +Another idiom where tuples can be very useful is when you are authoring +LINQ queries where the final result is a projection that contains some, but not +all, of the properties of the objects being selected. + +You would traditionally project the results of the query into a sequence +of objects that were an anonymous type. That presented many limitations, +primarily because anonymous types could not conveniently be named in the +return type for a method. Alternatives using `object` or `dynamic` as the +type of the result came with significant performance costs. + +Returning a sequence of a tuple type is easy, and the names and types +of the fields are available at compile time and through IDE tools. +For example, consider a ToDo application. You might define a +class similar to the following to represent a single entry in the ToDo list: + +[!code-csharp[ToDoItem](../../samples/snippets/csharp/tuples/tuples/projectionsample.cs#14_ToDoItem "To Do Item")] + +Your mobile applications may support a compact form of the current ToDo items +that only displays the title. That LINQ query would make a projection that +includes only the ID and the title. A method that returns a sequence of tuples +expresses that design very well: + +[!code-csharp[QueryReturningTuple](../../samples/snippets/csharp/tuples/tuples/projectionsample.cs#15_QueryReturningTuple "Query returning a tuple")] + +The named tuple can be part of the signature. It lets the compiler and IDE +tools provide static checking that you are using the result correctly. 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. + +## Deconstruction + +You can unpackage all the items in a tuple by *deconstructng* the tuple +returned by a method. There are two different approaches to deconstructing +tuples. First, you can explicitly declare the type of each field inside +parentheses to create discrete variables for each of the fields in the tuple: + +[!code-csharp[Deconstruct](../../samples/snippets/csharp/tuples/tuples/statistics.cs#10_Deconstruct "Deconstruct")] + +You can also declare implicitly typed variables for each field in a tuple +by using the `var` keyword outside the parentheses: + +[!code-csharp[DeconstructToVar](../../samples/snippets/csharp/tuples/tuples/statistics.cs#11_DeconstructToVar "Deconstruct to Var")] + +It is also legal to use the `var` keyword with any, or all of the variable +declarations inside the parentheses. + +```csharp +(double sum, var sumOfSquares, var count) = ComputeSumAndSumOfSquares(sequence); +``` +Note that you cannot use a specific +type outside the parentheses, even if every field in the tuple has the +same type. + +### Deconstring user defined types + +Any tuple type can be deconstructed as shown above. It's also easy +to enable deconstruction on any user defined type (classes, structs, or +even interfaces). + +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. For example, the following +`Person` type defines a `Deconstruct` method that deconstructs +a person object into the fields representing the first name +and last name: + +[!code-csharp[TypeWithDeconstructMethod](../../samples/snippets/csharp/tuples/tuples/person.cs#12_TypeWithDeconstructMethod "Type with a deconstruct method")] + +The deconstruct method enables assignment from a `Person` to two strings, +representing the `FirstName` and `LastName` properties: + +[!code-csharp[Deconstruct Type](../../samples/snippets/csharp/tuples/tuples/program.cs#12A_DeconstructType "Deconstruct a class type")] + +You can enable deconstruction even for types you did not author. +The `Deconstruct` method can be an extension method that unpackages +the accessible data members of an object. 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`: + +[!code-csharp[ExtensionDeconstructMethod](../../samples/snippets/csharp/tuples/tuples/person.cs#13_ExtensionDeconstructMethod "Type with a deconstruct extension method")] + +A `Student` object now has two accessible `Deconstruct` methods: the extension method +declared for `Student` types, and the member of the `Person` type. Both are in scope, +and that enables a `Student` to be deconstructed into either two variables or three. +If you assign a student to three variabless, the first name, last name, and GPA are +all returned. If you assign a student to two variables, only the first name and +the last name are returned. + +[!code-csharp[Deconstruct extension method](../../samples/snippets/csharp/tuples/tuples/program.cs#13A_DeconstructExtension "Deconstruct a class type using an extension method")] + +You should be very careful defining multiple `Deconstruct` methods in a +class or a class hierarchy. Multiple `Deconstruct` methods that have the +same number of `out` parameters can quickly cause ambiguities. Callers may +not be able to easily call the desired `Deconstruct` method. + +In this example, there is minimal chance for an ambiguious call because the +`Deconstruct` method for `Person` has two output parameters, and the `Deconstruct` +method for `Student` has three. + +## Conclusion + +The new language and library support for named tuples makes it much easier +to work with designs that use data structures that store multiple fields +but do not define behavior, as classes and structs do. It's +easy and concise to use tuples for those types. You get all the benefits of +static type checking, without needing to author types using the more +verbose `class` or `struct` syntax. Even so, they are most useful for utility methods +that are `private`, or `internal`. Create user defined types, either +`class` or `struct` types when your public methods return a value +that has multiple fields. diff --git a/docs/toc.md b/docs/toc.md index da74d5f41399f..cc0120119d055 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -258,7 +258,7 @@ ### [Basic Types](csharp/basic-types.md) ### [Classes](csharp/classes.md) ### [Structs](csharp/structs.md) -### [🔧 Tuples and unnamed types](csharp/tuples.md) +### [Tuples](csharp/tuples.md) ### [🔧 Interfaces](csharp/interfaces.md) ### [Methods and Lambda Expressions](csharp/methods-lambda-expressions.md) #### [Methods](csharp/methods.md) diff --git a/samples/snippets/csharp/new-in-7/new-in-7/Program.cs b/samples/snippets/csharp/new-in-7/new-in-7/Program.cs index 65c24036dc11a..b5ee466548f7a 100644 --- a/samples/snippets/csharp/new-in-7/new-in-7/Program.cs +++ b/samples/snippets/csharp/new-in-7/new-in-7/Program.cs @@ -124,7 +124,7 @@ private static void TupleMethod() } #region 08_TupleReturningMethod - private static (int max, int min) Range(IEnumerable numbers) + private static (int Max, int Min) Range(IEnumerable numbers) { int min = int.MaxValue; int max = int.MinValue; @@ -145,15 +145,15 @@ private static void TupleDeclarations() #endregion #region 05_NamedTuple - (string alpha, string beta) namedLetters = ("a", "b"); + (string Alpha, string Beta) namedLetters = ("a", "b"); #endregion #region 06_ImplicitNamedTuple - var alphabetStart = (alpha: "a", beta: "b"); + var alphabetStart = (Alpha: "a", Beta: "b"); #endregion #region 07_NamedTupleConflict - (string first, string second) firstLetters = (alpha: "a", beta: "b"); + (string First, string Second) firstLetters = (Alpha: "a", Beta: "b"); #endregion } diff --git a/samples/snippets/csharp/tuples/tuples.sln b/samples/snippets/csharp/tuples/tuples.sln new file mode 100644 index 0000000000000..52634fdf48ea0 --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.25906.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tuples", "tuples\tuples.csproj", "{9B28122A-B30F-4318-9245-E016004A206C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9B28122A-B30F-4318-9245-E016004A206C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B28122A-B30F-4318-9245-E016004A206C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B28122A-B30F-4318-9245-E016004A206C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B28122A-B30F-4318-9245-E016004A206C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/snippets/csharp/tuples/tuples/App.config b/samples/snippets/csharp/tuples/tuples/App.config new file mode 100644 index 0000000000000..88fa4027bda39 --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/snippets/csharp/tuples/tuples/Person.cs b/samples/snippets/csharp/tuples/tuples/Person.cs new file mode 100644 index 0000000000000..4cc19d260ec84 --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/Person.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace tuples +{ + #region 12_TypeWithDeconstructMethod + 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; + } + } + #endregion + + #region 13_ExtensionDeconstructMethod + 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; + } + } + #endregion +} diff --git a/samples/snippets/csharp/tuples/tuples/Program.cs b/samples/snippets/csharp/tuples/tuples/Program.cs new file mode 100644 index 0000000000000..4a7383c8c3df3 --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/Program.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace tuples +{ + class Program + { + static void Main(string[] args) + { + InitializationStatements(); + + AssignmentStatements(); + + var sample = new List{ 2.0, 4.0, 6.0, 8.0 }; + + Console.WriteLine(StatisticsVersionOne.StandardDeviation(sample)); + + Console.WriteLine(StatisticsVersionTwo.StandardDeviation(sample)); + Console.WriteLine(StatisticsVersionThree.StandardDeviation(sample)); + Console.WriteLine(StatisticsVersionFour.StandardDeviation(sample)); + + #region 12A_DeconstructType + var p = new Person("Althea", "Goodwin"); + var (first, last) = p; + #endregion + Console.WriteLine(first); + Console.WriteLine(last); + + #region 13A_DeconstructExtension + var s1 = new Student("Cary", "Totten", 4.5); + var (fName, lName, gpa) = s1; + #endregion + + var (f, l) = s1; + + var source = new ProjectionSample(); + var sequence = source.GetCurrentItemsMobileList(); + foreach (var item in sequence) + Console.WriteLine($"{item.ID}, {item.Title}"); + + } + + private static void AssignmentStatements() + { + #region 03_VariableCreation + // 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"); + #endregion + + #region 04_VariableAssignment + // unnamed to named: + unnamed = named; + + // named to unnamed: + 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}"); + #endregion + + } + + private static void InitializationStatements() + { + #region 01_UnNamedTuple + var unnamed = ("one", "two"); + #endregion + + #region 02_NamedTuple + var named = (first: "one", second: "two"); + #endregion + } + + private static double VersionThree(IEnumerable sequence) + { + var computation = (Sum: 0.0, SumOfSquares: 0.0, Items: 0); + + foreach (var item in sequence) + { + computation.Items++; + computation.Sum += item; + computation.SumOfSquares += item * item; + } + + var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Items; + return Math.Sqrt(variance / computation.Items); + } + + private static double VersionFour(IEnumerable sequence) + { + var coreStats = ComputeCoreStats(sequence); + + var variance = coreStats.sumOfSquares - coreStats.sum * coreStats.sum / coreStats.items; + return Math.Sqrt(variance / coreStats.items); + } + + // Remove names, and it can be an unnamed tuple. + private static (int items, double sum, double sumOfSquares) ComputeCoreStats(IEnumerable sequence) + { + double total = 0; + double sumOfSquares = 0; + int items = 0; + + foreach (var item in sequence) + { + items++; + total += item; + sumOfSquares += item * item; + } + return (items, total, sumOfSquares); + } + } +} diff --git a/samples/snippets/csharp/tuples/tuples/ProjectionSample.cs b/samples/snippets/csharp/tuples/tuples/ProjectionSample.cs new file mode 100644 index 0000000000000..c0b6329ec98c1 --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/ProjectionSample.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace tuples +{ + #region 14_ToDoItem + 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; } + } + #endregion + + + public class ProjectionSample + { + private List AllItems = new List + { + new ToDoItem + { + ID=0, + IsDone = false, + DueDate = DateTime.Now + TimeSpan.FromDays(2), + Title = "answer email", + Notes = "This happens every day" + }, + new ToDoItem + { + ID=1, + IsDone = false, + DueDate = DateTime.Now + TimeSpan.FromDays(3), + Title = "Review open PRs", + Notes = "Look for working code, good tests, and structure" + }, + new ToDoItem + { + ID=2, + IsDone = false, + DueDate = DateTime.Now + TimeSpan.FromDays(1), + Title = "Create updated issues", + Notes = "Need to track what's getting done" + }, + new ToDoItem + { + ID=3, + IsDone = false, + DueDate = DateTime.Now + TimeSpan.FromDays(5), + Title = "Plan vacation", + Notes = "The first step is determining where we should go" + }, + new ToDoItem + { + ID=4, + IsDone = false, + DueDate = DateTime.Now + TimeSpan.FromDays(7), + Title = "make reservations for vacation", + Notes = "Once we decide on a location, find lodging" + } + }; + + #region 15_QueryReturningTuple + internal IEnumerable<(int ID, string Title)> GetCurrentItemsMobileList() + { + return from item in AllItems + where !item.IsDone + orderby item.DueDate + select (item.ID, item.Title); + } + #endregion + } +} diff --git a/samples/snippets/csharp/tuples/tuples/Properties/AssemblyInfo.cs b/samples/snippets/csharp/tuples/tuples/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..4e94284514579 --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("tuples")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("tuples")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9b28122a-b30f-4318-9245-e016004a206c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/snippets/csharp/tuples/tuples/Statistics.cs b/samples/snippets/csharp/tuples/tuples/Statistics.cs new file mode 100644 index 0000000000000..9d02f81a0c90a --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/Statistics.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace tuples +{ + public static class StatisticsVersionOne + { + #region 05_StandardDeviation + public static double StandardDeviation(IEnumerable 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; + } + #endregion + } + + public static class StatisticsVersionTwo + { + #region 06_SumOfSquaresFormula + public static double StandardDeviation(IEnumerable 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); + } + #endregion + } + + public static class StatisticsVersionThree + { + #region 07_TupleVersion + public static double StandardDeviation(IEnumerable 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); + } + #endregion + } + + public static class StatisticsVersionFour + { + #region 08_TupleMethodVersion + public static double StandardDeviation(IEnumerable sequence) + { + (int Count, double Sum, double SumOfSquares) computation = ComputeSumsAnSumOfSquares(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) ComputeSumsAnSumOfSquares(IEnumerable 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; + } + #endregion + } + + public static class StatisticsVersionFive + { + #region 09_CleanedTupleVersion + public static double StandardDeviation(IEnumerable 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 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); + } + #endregion + } + + + public static class StatisticsVersionSix + { + #region 10_Deconstruct + public static double StandardDeviation(IEnumerable sequence) + { + (int count, double sum, double sumOfSquares) = ComputeSumAndSumOfSquares(sequence); + + var variance = sumOfSquares - sum * sum / count; + return Math.Sqrt(variance / count); + } + #endregion + + private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable 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); + } + } + + public static class StatisticsVersionSeven + { + #region 11_DeconstructToVar + public static double StandardDeviation(IEnumerable sequence) + { + var (sum, sumOfSquares, count) = ComputeSumAndSumOfSquares(sequence); + + var variance = sumOfSquares - sum * sum / count; + return Math.Sqrt(variance / count); + } + #endregion + + private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable 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); + } + } +} diff --git a/samples/snippets/csharp/tuples/tuples/packages.config b/samples/snippets/csharp/tuples/tuples/packages.config new file mode 100644 index 0000000000000..f9c3485db8966 --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/snippets/csharp/tuples/tuples/tuples.csproj b/samples/snippets/csharp/tuples/tuples/tuples.csproj new file mode 100644 index 0000000000000..0b67c09df8e5c --- /dev/null +++ b/samples/snippets/csharp/tuples/tuples/tuples.csproj @@ -0,0 +1,60 @@ + + + + + Debug + AnyCPU + {9B28122A-B30F-4318-9245-E016004A206C} + Exe + tuples + tuples + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\packages\System.ValueTuple.4.3.0-preview1-24530-04\lib\netstandard1.0\System.ValueTuple.dll + True + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file