Will a Future Version of .Net Support Tuples in C#

Will a future version of .NET support tuples in C#?

I've just read this article from the MSDN Magazine: Building Tuple

Here are excerpts:

The upcoming 4.0 release of Microsoft
.NET Framework introduces a new type
called System.Tuple. System.Tuple is a
fixed-size collection of
heterogeneously typed data.
 
 

 

Like an array, a tuple has a fixed
size that can't be changed once it has
been created. Unlike an array, each
element in a tuple may be a different
type, and a tuple is able to guarantee
strong typing for each element.

 

There is already one example of a
tuple floating around the Microsoft
.NET Framework, in the
System.Collections.Generic namespace:
KeyValuePair. While KeyValuePair can be thought of as the same
as Tuple, since they are both
types that hold two things,
KeyValuePair feels different from
Tuple because it evokes a relationship
between the two values it stores (and
with good reason, as it supports the
Dictionary class).

Furthermore, tuples can be arbitrarily
sized, whereas KeyValuePair holds only
two things: a key and a value.


While some languages like F# have special syntax for tuples, you can use the new common tuple type from any language. Revisiting the first example, we can see that while useful, tuples can be overly verbose in languages without syntax for a tuple:

class Program {
static void Main(string[] args) {
Tuple<string, int> t = new Tuple<string, int>("Hello", 4);
PrintStringAndInt(t.Item1, t.Item2);
}
static void PrintStringAndInt(string s, int i) {
Console.WriteLine("{0} {1}", s, i);
}
}

Using the var keyword from C# 3.0, we can remove the type signature on the tuple variable, which allows for somewhat more readable code.

var t = new Tuple<string, int>("Hello", 4);

We've also added some factory methods to a static Tuple class which makes it easier to build tuples in a language that supports type inference, like C#.

var t = Tuple.Create("Hello", 4);

Is Using .NET 4.0 Tuples in my C# Code a Poor Design Decision?

Tuples are great if you control both creating and using them - you can maintain context, which is essential to understanding them.

On a public API, however, they are less effective. The consumer (not you) has to either guess or look up documentation, especially for things like Tuple<int, int>.

I would use them for private/internal members, but use result classes for public/protected members.

This answer also has some info.

what tuples are good for?

Here are a few resources that mention the purposes of Tuples, how they can be used and what they can be used for:

The Practical Usage of Tuples in C#

Making Use of Tuples

Using Tuples in .NET 4.0 | Stack Overflow

Tuple Support in C# | Stack Overflow

Usages and Advantages of Tuples | DotNetSpeaks

Summary:

Basically, the Tuple functions as a convenient way to NOT make a custom class to accomplish something. It is highly flexible and could provide a variety of uses, however since it can be very generic, you should be sure to properly document its usage.

Maximum bug-free and future compatibility: retarget to a higher .NET version?

I wouldn't say that it's a "buggy piece of crap", but each version generally fixes a number of issues, adds new features, and improves perfomance (GC and JIT in particular). Switching to .NET 4.8 would likely be as simple as installing VS2019, allowing it to update the project files as needed, and changing the project Framework version.

One thing to keep in mind is that .NET 4.8 is likely the last version of .NET Framework, so a more future proof approach would be to port the app to .NET Core. The next version of .NET is called .NET 5 (expected release date is 12/2020), and will be based on .NET Core.

Equivalent of Tuple (.NET 4) for .NET Framework 3.5

No, not in .Net 3.5. But it shouldn't be that hard to create your own.

public class Tuple<T1, T2>
{
public T1 First { get; private set; }
public T2 Second { get; private set; }
internal Tuple(T1 first, T2 second)
{
First = first;
Second = second;
}
}

public static class Tuple
{
public static Tuple<T1, T2> New<T1, T2>(T1 first, T2 second)
{
var tuple = new Tuple<T1, T2>(first, second);
return tuple;
}
}

UPDATE: Moved the static stuff to a static class to allow for type inference. With the update you can write stuff like var tuple = Tuple.New(5, "hello"); and it will fix the types for you implicitly.

What requirement was the tuple designed to solve?

When writing programs it is extremely common to want to logically group together a set of values which do not have sufficient commonality to justify making a class.

Many programming languages allow you to logically group together a set of otherwise unrelated values without creating a type in only one way:

void M(int foo, string bar, double blah)

Logically this is exactly the same as a method M that takes one argument which is a 3-tuple of int, string, double. But I hope you would not actually make:

class MArguments
{
public int Foo { get; private set; }
... etc

unless MArguments had some other meaning in the business logic.

The concept of "group together a bunch of otherwise unrelated data in some structure that is more lightweight than a class" is useful in many, many places, not just for formal parameter lists of methods. It's useful when a method has two things to return, or when you want to key a dictionary off of two data rather than one, and so on.

Languages like F# which support tuple types natively provide a great deal of flexibility to their users; they are an extremely useful set of data types. The BCL team decided to work with the F# team to standardize on one tuple type for the framework so that every language could benefit from them.

However, there is at this point no language support for tuples in C#. Tuples are just another data type like any other framework class; there's nothing special about them. We are considering adding better support for tuples in hypothetical future versions of C#. If anyone has any thoughts on what sort of features involving tuples you'd like to see, I'd be happy to pass them along to the design team. Realistic scenarios are more convincing than theoretical musings.

How to separate C# tuple values to match method arguments

C# is a strongly typed language, so you cannot pass tuple (which has its own class ValueTuple class).

So, you could just define overload for the method:

public void Test()
{
ReceiveMultipleArguments(GetTuple());
}

public (int, object) GetTuple()
{
return (5, null);
}

public void ReceiveMultipleArguments((int a, object b) @params) => ReceiveMultipleArguments(@params.a, @params.b);
public void ReceiveMultipleArguments(int a, object b) { ... }

What are single and zero element tuples good for?

What good is a 0-tuple?

A 2-tuple or a 3-tuple represent a group of related items. (Points in 2D space, RGB values of a color, etc.) A 1-tuple is not very useful since it could easily be replaced with a single int.

A 0-tuple seems even more useless since it contains absolutely nothing. Yet it has properties that make it very useful in functional languages like F#. For example, the 0-tuple type has exactly one value, usually represented as (). All 0-tuples have this value so it's essentially a singleton type. In most functional programming languages, including F#, this is called the unit type.

Functions that return void in C# will return the unit type in F#:

let printResult = printfn "Hello"

Run that in the F# interactive interpreter, and you'll see:

val printResult : unit = ()

This means that the value printResult is of type unit, and has the value () (the empty tuple, the one and only value of the unit type).

Functions can take the unit type as a parameter, too. In F#, functions may look like they're taking no parameters. But in fact, they're taking a single parameter of type unit. This function:

let doMath() = 2 + 4

is actually equivalent to:

let doMath () = 2 + 4

That is, a function that takes one parameter of type unit and returns the int value 6. If you look at the type signature that the F# interactive interpreter prints when you define this function, you'll see:

val doMath : unit -> int

The fact that all functions will take at least one parameter and return a value, even if that value is sometimes a "useless" value like (), means that function composition is a lot easier in F# than in languages that don't have the unit type. But that's a more advanced subject which we'll get to later on. For now, just remember that when you see unit in a function signature, or () in a function's parameters, that's the 0-tuple type that serves as the way to say "This function takes, or returns, no meaningful values."

Does C# 8 support the .NET Framework?

Yes, C# 8 can be used with the .NET Framework and other targets older than .NET Core 3.0/.NET Standard 2.1 in Visual Studio 2019 (or older versions of Visual Studio if you install a NuGet package).

The only thing required is to set language version to 8.0 in the csproj file. You can also do this in Directory.Build.props to apply it to all projects in your solution. Read below for how to do this in Visual Studio 2019, version 16.3 and newer.

Most - but not all - features are available whichever framework is targeted.



Features that work

The following features are syntax changes only; they work regardless of framework:

  • Static local functions
  • Using declarations
  • Null-coalescing assignment
  • Readonly members
  • Disposable ref structs
  • Positional patterns
  • Tuple patterns
  • Switch expressions
  • Nullable reference types are also supported, but the new nullable attributes required to design the more complex nullable use cases are not. I cover this in more detail further down in the "gory details" section.

Features that can be made to work

These require new types which are not in the .NET Framework. They can only be used in conjunction with "polyfill" NuGet packages or code files:

  • Asynchronous streams - Microsoft.Bcl.AsyncInterfaces
  • Indices and ranges

Default interface members - do not, cannot, and never will work

Default interface members won't compile under .NET Framework and will never work because they require runtime changes in the CLR. The .NET CLR is now frozen as .NET Core is now the way forward.

For more information on what does and doesn't work, and on possible polyfills, see Stuart Lang's article, C# 8.0 and .NET Standard 2.0 - Doing Unsupported Things.



Code

The following C# project targetting .NET Framework 4.8 and using C# 8 nullable reference types compiles in Visual Studio 16.2.0. I created it by choosing the .NET Standard Class Library template and then editing it to target .NET Framework instead:

.csproj:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

.cs:

namespace ClassLibrary1
{
public class Class1
{
public string? NullableString { get; set; }
}
}

I then tried a .NET Framework 4.5.2 WinForms project, using a legacy .csproj format, and added the same nullable reference type property. I changed the language type in the Visual Studio Advanced Build settings dialog (disabled in 16.3) to latest and saved the project. Of course as this point it doesn't build. I opened the project file in a text editor and changed latest to preview in the build configuration PropertyGroup:

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<LangVersion>preview</LangVersion>

I then enabled support for nullable reference types by adding <Nullable>enable</Nullable> to the main PropertyGroup:

<PropertyGroup>
<Nullable>enable</Nullable>

I reloaded the project, and it builds.



Visual Studio 2019

There has been a major change in the RTM version of Visual Studio 2019 version 16.3, the launch version for C# 8.0: the language selection dropdown has been disabled:

Sample Image

Microsoft's rationale for this is:

Moving forward, ... each version of each framework will have a single
supported and default version, and we won't support arbitrary
versions. To reflect this change in support, this commit permanently
disables the language version combo box and adds a link to a document
explaining the change.

The document which opens is C# language versioning. This lists C# 8.0 as the default language for .NET Core 3.x ONLY. It also confirms that each version of each framework will, going forward, have a single supported and default version and that the framework-agnosticism of the language can no longer be relied on.

The language version can still be forced to 8 for .NET Framework projects by editing the .csproj file.



The gory details

When this answer was first written, C# 8 was in preview and a lot of detective work was involved. I leave that information here for posterity. Feel free to skip it if you don't need to know all the gory details.

The C# language has historically been mostly framework neutral - i.e. able to compile older versions of the Framework - although some features have required new types or CLR support.

Most C# enthusiasts will have read the blog entry Building C# 8.0 by Mads Torgersen, which explains that certain features of C# 8 have platform dependencies:

Async streams, indexers and ranges all rely on new framework types
that will be part of .NET Standard 2.1... .NET Core 3.0 as well as
Xamarin, Unity and Mono will all implement .NET Standard 2.1, but .NET
Framework 4.8 will not. This means that the types required to use
these features won’t be available on .NET Framework 4.8.

This looks a bit like Value Tuples which were introduced in C# 7. That feature required new types - the ValueTuple structures - which were not available in NET Framework versions below 4.7 or .NET Standard older than 2.0. However, C# 7 could still be used in older versions of .NET, either without value tuples or with them by installing the System.ValueTuple Nuget package. Visual Studio understood this, and all was fine with the world.

However, Mads also wrote:

For this reason, using C# 8.0 is only supported on platforms that implement .NET Standard 2.1.

...which if true would have ruled out using C# 8 with any version of the .NET Framework, and indeed even in .NET Standard 2.0 libraries which only recently we were encouraged to use as a baseline target for library code. You wouldn't even be able to use it with .NET Core versions older than 3.0 as they too only support .NET Standard 2.0.

The investigation was on! -

  • Jon Skeet has an alpha version of Noda-Time using C# 8 ready to go which targets .NET Standard 2.0 only. He is clearly expecting C# 8/.NET Standard 2.0 to support all frameworks in the .NET family. (See also Jon's blog post "First steps with nullable reference types").

  • Microsoft employees have been discussing the Visual Studio UI for C# 8 nullable reference types on GitHub, and it is stated that they intend to support the legacy csproj (pre-.NET Core SDK format csproj). This is a very strong indication that C# 8 will be usable with the .NET Framework. [I suspect they will backtrack on this now that the Visual Studio 2019 language version dropdown has been disabled and .NET has been tied to C# 7.3]

  • Shortly after the famous blog post, a GitHub thread discussed cross-platform support. An important point which emerged was that .NET Standard 2.1 will include a marker that denotes that default implementations of interfaces is supported - the feature requires a CLR change that will never be available to the .NET Framework. Here's the important bit, from Immo Landwerth, Program Manager on the .NET team at Microsoft:

Compilers (such as C#) are expected to use the presence of this field to decide whether or not to allow default interface implementations. If the field is present, the runtime is expected to be able to load & execute the resulting code.

  • This all pointed to "C# 8.0 is only supported on platforms that implement .NET Standard 2.1" being an oversimplification, and that C# 8 will support the .NET Framework but, as there is so much uncertainty, I asked on GitHub and HaloFour answered:

IIRC, the only feature that definitely won't appear on .NET Framework is DIM (default interface methods) as that requires runtime changes. The other features are driven by the shape of classes that might never be added to the .NET Framework but can be polyfilled through your own code or NuGet (ranges, indexes, async iterators, async disposal).

  • Victor Derks commented that "The new nullable attributes required to design the more complex nullable use cases are only available in System.Runtime.dll that ships with .NET Core 3.0 and .NET Standard 2.1... [and] incompatible with .NET Framework 4.8"

  • However, Immo Landwerth commented that "The vast majority of our APIs didn't need any custom attributes as the types are either fully generic or not-null" under the article Try out Nullable Reference Types

  • Ben Hall raised the issue Availability of nullable attributes outside of Core 3.0 on GitHub, with the following comments from Microsoft employees being of note:

C# 8 will be fully supported on .net core 3.0 and .net standard 2.1 only.
If you manually edit the project file to use C# 8 with .net core 2.1,
you are in unsupported territory. Some C# 8 features will happen to
work well, some C# 8 features will work not too well (e.g. poor
performance), some C# 8 features will work with extra hacks, and some
C# 8 features will not work at all. Very complex to explain. We do not
actively block it so the expert users who can navigate through it can
do so. I would not recommend this unsupported mix&match to be used
broadly.

(Jan Kotas)

People like you who are willing understand -- and work around them --
are free to use C# 8. The point is, not all language features will work
on down-level targets.

(Immo Landwerth)



Caveat emptor

The C# 8/.NET Framework combination is not officially supported by Microsoft. It is, they say, for experts only.



Related Topics



Leave a reply



Submit