Working with Tuples - going deeper
I will now start adding more meat to the dummy implementation of the GetAverageAndCount()
method we created in the previous recipe. If you are new to Tuples, and have not worked through the previous recipe, I encourage you to do so first before starting to work through this recipe.
Getting ready
You need to have completed the code steps in the recipe Working with Tuples - getting started, in order to work through this recipe. Ensure that you have added the required NuGet package as specified in the previous recipe.
How to do it...
- Let's take a look at the calling code again. We can further simplify the code in the
static void Main
method by getting rid of thevar s
. When we called theGetAverageAndCount()
method, we returned the Tuple intovar s
.
var s = ch1.GetAverageAndCount(scores);
- We do not have to do this. C# 7.0 allows us to immediately split the Tuple into its respective parts as follows:
var (average, studentCount) = ch1.GetAverageAndCount(scores);
- We can now consume the values returned by the Tuple directly as follows:
WriteLine($"Average was {average} across {studentCount} students");
- Before we implement the
GetAverageAndCount()
method, make sure that yourstatic void Main
method looks as follows:
static void Main(string[] args) { int[] scores = { 17, 46, 39, 62, 81, 79, 52, 24 }; Chapter1 ch1 = new Chapter1(); var (average, studentCount) = ch1.GetAverageAndCount(scores); WriteLine($"Average was {average} across { studentCount} students"); ReadLine(); }
- Secondly, ensure that the
GetAverageAndCount()
method's dummy implementation looks as follows:
public (int average, int studentCount) GetAverageAndCount(int[] scores) { var returnTuple = (ave:0, sCount:0); return returnTuple; }
- Go ahead and run your console application. You will see that the two values,
average
andstudentCount
are returned from our dummy implementation ofGetAverageAndCount()
.

- The values are obviously still zero because we have not defined any logic inside the method. We will do this next. Before we write the implementation, make sure that you have added the following
using
statement:
using System.Linq;
- Because we are using an array of integers for the variable
scores
, we can easily return the results we need. LINQ allows us to get the sum of the student scores contained in thescores
array, simply by writingscores.Sum()
. We can also easily get the count of the student scores from thescores
array by writingscores.Count()
. The average, therefore, would logically be the sum of the scores divided by the count of the student scores(scores.Sum()/scores.Count())
. We then put the values into ourreturnTuple
literal as follows:
public (int average, int studentCount) GetAverageAndCount(int[] scores) { var returnTuple = (ave:0, sCount:0); returnTuple = (returnTuple.ave = scores.Sum()/scores.Count(), returnTuple.sCount = scores.Count()); return returnTuple; }
- Run your console application to see the result displayed as follows:

- We can see that the class average isn't too great, but that is of little importance to our code. Another piece of code that isn't too great is this line:
returnTuple = (returnTuple.ave = scores.Sum()/scores.Count(), returnTuple.sCount = scores.Count());
- It is clunky and doesn't read very nicely. Let's simplify this a bit. Remember that I mentioned previously that Tuples play nicely together as long as their types match? This means that we can do this:
public (int average, int studentCount) GetAverageAndCount(int[] scores) { var returnTuple = (ave:0, sCount:0); returnTuple = (scores.Sum()/scores.Count(), scores.Count()); return returnTuple; }
- Run your console application again and notice that the result stays the same:

- So why did we give the Tuple literal names to begin with? Well, it allows you to reference them easily within your
GetAverageAndCount()
method. It is also really very useful when using aforeach
loop in your method. Consider the following scenario. In addition to returning the count and average of the student scores, we need to return an additional Boolean value if the class average is below a certain threshold. For this example, we will be making use of an extension method calledCheckIfBelowAverage()
and it will take athreshold
value as an integer parameter. Start off by creating a new static class calledExtensionMethods
.
public static class ExtensionMethods { }
- Inside the
static
class, create a new method calledCheckIfBelowAverage()
and pass it an integer value calledthreshold
. The implementation of this extension method is pretty straightforward, so I will not go into much detail here.
public static bool CheckIfBelowAverage( this int classAverage, int threshold) { if (classAverage < threshold) { // Notify head of department return true; } else return false; }
- In the
Chapter1
class, overload theGetAverageAndCount()
method by changing its signature and passing a value for the threshold that needs to be applied. You will remember that I mentioned that a Tuple return type method can return several values, not just two. In this example, we are returning a third value calledbelowAverage
that will indicate if the calculated class average is below the threshold value we pass to it.
public (int average, int studentCount, bool belowAverage) GetAverageAndCount(int[] scores, int threshold) { }
- Modify the Tuple literal, adding it to
subAve
,and default it totrue
, because a class average of zero will logically be below any threshold value we pass to it.
var returnTuple = (ave: 0, sCount: 0, subAve: true);
- We can now call the extension method
CheckIfBelowAverage()
on thereturnTuple.ave
value we defined in our Tuple literal and pass through it thethreshold
variable. Just how useful giving the Tuple literal logical names becomes evident when we use it to call the extension method.
returnTuple = (scores.Sum() / scores.Count(), scores.Count(), returnTuple.ave.CheckIfBelowAverage(threshold));
- Your completed
GetAverageAndCount()
method will now look as follows:
public (int average, int studentCount, bool belowAverage) GetAverageAndCount(int[] scores, int threshold) { var returnTuple = (ave: 0, sCount: 0, subAve: true); returnTuple = (scores.Sum() / scores.Count(), scores.Count(), returnTuple.ave.CheckIfBelowAverage(threshold)); return returnTuple; }
- Modify your calling code to make use of the overloaded
GetAverageAndCount()
method as follows:
int threshold = 51; var (average, studentCount, belowAverage) = ch1.GetAverageAndCount( scores, threshold);
- Lastly, modify the interpolated string to read as follows:
WriteLine($"Average was {average} across {studentCount} students. {(average < threshold ? " Class score below average." : " Class score above average.")}");
- The completed code in your
static void Main
method should now look as follows:
static void Main(string[] args) { int[] scores = { 17, 46, 39, 62, 81, 79, 52, 24 }; Chapter1 ch1 = new Chapter1(); int threshold = 51; var (average, studentCount, belowAverage) = ch1.GetAverageAndCount(scores, threshold); WriteLine($"Average was {average} across {studentCount} students. {(average < threshold ? " Class score below average." : " Class score above average.")}"); ReadLine(); }
- Run your console application to view the result.

- To test that the ternary operator
?
is working correctly inside the interpolated string, modify your threshold value to be lower than the average returned.
int threshold = 40;
- Running your console application a second time will result in a passing average class score.

- Finally, there is one glaring problem that I need to highlight with this recipe. It is one that I am sure you have picked up on already. If not, don't worry. It is a bit of a sneaky one. This is the gotcha I was referring to at the start of this recipe and I intentionally wanted to include it to illustrate the bug in the code. Our array of student scores is defined as follows:
int[] scores = { 17, 46, 39, 62, 81, 79, 52, 24 };
- The sum of these equals to 400 and because there are only 8 scores, the value will work out correctly because it divides up to a whole number (400 / 8 = 50). But what would happen if we had another student score in there? Let's take a look. Modify your scores array as follows:
int[] scores = { 17, 46, 39, 62, 81, 79, 52, 24, 49 };
- Run your console application again and look at the result.

- The problem here is that the average is incorrect. It should be 49.89. We know that we want a double (unless your application of this is intended to return an integer). We, therefore, need to pay attention to casting the values correctly in the return type and the Tuple literal. We also need to handle this in the extension method
CheckIfBelowAverage()
. Start off by changing the extension method signature as follows to act on a double.
public static bool CheckIfBelowAverage( this double classAverage, int threshold) { }
- Then we need to change the data type of the
average
variable in the Tuple method return type as follows:
public (double average, int studentCount, bool belowAverage) GetAverageAndCount(int[] scores, int threshold) { }
- Then, modify the Tuple literal so
ave
is a double by usingave: 0D
.
var returnTuple = (ave: 0D, sCount: 0, subAve: true);
- Cast the average calculation to a
double
.
returnTuple = ((double)scores.Sum() / scores.Count(), scores.Count(), returnTuple.ave.CheckIfBelowAverage(threshold));
- Add the following
using
statement to your application:
using static System.Math;
- Lastly, use the
Round
method to format theaverage
variable in the interpolated string to two decimals.
WriteLine($"Average was {Round(average,2)} across {studentCount} students. {(average < threshold ? " Class score below average." : " Class score above average.")}");
- If everything is done correctly, your
GetAverageAndCount()
method should look as follows:
public (double average, int studentCount, bool belowAverage) GetAverageAndCount(int[] scores, int threshold) { var returnTuple = (ave: 0D, sCount: 0, subAve: true); returnTuple = ((double)scores.Sum() / scores.Count(), scores.Count(), returnTuple.ave.CheckIfBelowAverage( threshold)); return returnTuple; }
- Your calling code should also look as follows:
static void Main(string[] args) { int[] scores = { 17, 46, 39, 62, 81, 79, 52, 24, 49 }; Chapter1 ch1 = new Chapter1(); int threshold = 40; var (average, studentCount, belowAverage) = ch1.GetAverageAndCount(scores, threshold); WriteLine($"Average was {Round(average,2)} across {studentCount} students. {(average < threshold ? " Class score below average." : " Class score above average.")}"); ReadLine(); }
- Run the console application to see the correctly rounded average for the student scores.

How it works...
Tuples are structs, and therefore value types that are created locally. You, therefore, do not have to worry about using and assigning Tuples on-the-fly or that it creating a lot of allocations. Their contents are merely copied when passed. Tuples are mutable and the elements are publicly scoped mutable fields. Using the code example in this recipe, I can, therefore, do the following:
returnTuple = (returnTuple.ave + 15, returnTuple.sCount - 1);
C# 7.0 is allowing me to first update the average value (shifting the average up) and then decrementing the count field. Tuples are a very powerful feature of C# 7.0, and it will be of great benefit to many developers when implemented it correctly.