.NET Zone is brought to you in partnership with:

Sasha Goldshtein is a Senior Consultant for Sela Group, an Israeli company specializing in training, consulting and outsourcing to local and international customers.Sasha's work is divided across these three primary disciplines. He consults for clients on architecture, development, debugging and performance issues; he actively develops code using the latest bits of technology from Microsoft; and he conducts training classes on a variety of topics, from Windows Internals to .NET Performance. You can read more about Sasha's work and his latest ventures at his blog: http://blogs.microsoft.co.il/blogs/sasha. Sasha writes from Herzliya, Israel. Sasha is a DZone MVB and is not an employee of DZone and has posted 202 posts at DZone. You can read more from them at their website. View Full User Profile

Easier Tuple-Like Classes in C#

03.03.2013
| 3365 views |
  • submit to reddit

A discussion during the MVP Summit prompted me to think about what would make it easier to use tuple-like classes while preserving valuable naming information of the tuple's constituents. The purpose of this exercise is to try and come up with a solution that does not require modification of existing C# syntax.


To set the scene, consider the following method:

bool ParseRequest(string request, out string operation, out int id) {
	string[] parts;
	if (request == null || (parts = request.Split(' ')).Length != 2) {
		return false;
	}
	operation = parts[1];
	return int.TryParse(parts[0], out id);
}

Using this method entails the very inconvenient syntax typical for out parameters:

string request = ...;
string operation;
int id;
if (ParseRequest(request, out operation, out id)) ...

We can resort to tuples, but it makes things that much uglier on the caller's side:

Tuple<bool, string, int> ParseRequest(string request) ...

var result = ParseRequest(request);
if (result.Item1) ... //continue to use result.Item2, result.Item3

One alternative that requires syntax changes relies on providing methods with multiple return values, or at least syntax for unpacking tuples such as the following:

(success, operation, id) = ParseRequest(request);
if (success) ...

In the latter syntax, the types of success, operation, and id would be implicitly determined by the compiler based on the return type of the ParseRequest method. But again, that requires syntax changes. Here's an idea that doesn't:

struct ParseResult {
	public bool Success;
	public string Operation;
	public int Id;
}

ParseResult ParseRequest(string request) {
	//proceeds to return new ParseResult { ... }
}

This is nice, but loses many of the advantages that the Tuple class has, such as immutability. How about we combine the two?

class ParseResult : Tuple<bool, string, int> {}

That's bad for the caller -- we still have to access the Item1Item2, and Item3 properties. In other words, we need renaming:

class ParseResult : Tuple<bool, string, int> {
	public bool Success { return Item1; }
	public string Operation { return Item2; }
	public int Id { return Item3; }
}

Now, this is the kind of thing compilers are good at. It is tempting to go back to the drawing board and add some auxiliary syntax, maybe something like:

class ParseResult (bool success, string operation, int id) {}

The compiler could then go ahead and generate exactly the same thing as the previous snippet. If you don't like the parens before the class definition, I guess we could settle for the following:

[RenamedTuple("success", "operation", "id")]
class ParseResult : Tuple<bool, string, int> {}

This is even something you could easily feed into an AOP framework and expect it to generate the boilerplate code for you. Alternatively, if you can live with the perf hit introduced by dynamic invocation, you could have the following:

class ParseResult : DynamicTuple<bool, string, int> {
	public ParseResult() : base("success", "operation", "id") {}
}

Here, the DynamicTuple class can be essentially a dynamic key-value store that would provide the necessarySuccessOperation, and Id properties for the caller, as well as possibly a convenient constructor-like initialization syntax for the callee.

There are probably dozens of additional solutions that all nudge the verbosity in different directions, but nothing that can be concise without introducing additional syntax to the language. Hopefully this illustrates that it's easy to complain about the language, but not easy at all to come up with a satisfactory alternative that would be agreeable for everyone :-)

Published at DZone with permission of Sasha Goldshtein, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)