I'm working on the architecture for what is essentially a document parsing and analysis framework. Given the lines of the document, the framework will ultimately produce a large object (call it Document) representing the document.
Early filters in the pipeline will need to operate on a line-by-line basis. However, filters further down will need to transform (and ultimately produce) the Document object.
To implement this, I was thinking of using a filter definition like this:
public interface IFilter<in TIn, out TOut> {
TOut Execute(TIn data);
}
All filters will be registered with a PipelineManager class (as opposed to using the 'linked-list' style approach.) Before executing, PipelineManager will verify the integrity of the pipeline to ensure that no filter is given the wrong input type.
My question: Is it architecturally sound to have a pipeline with a changing data type (i.e. a good idea)?
P.S. The reason I'm implementing my application as a pipeline is because I feel it will be easy for plugin authors to replace/extend existing filters. Just swap out the filter you want to change with a different implementation, and you're set.
EDIT: Note, have removed other answer to replace with this wall'o'text grin
NINJAEDIT: Fun fact: Powershell (mentioned in #Loudenvier's answer) was once going to be named 'Monad' - also, found Wes Dyer's blog post on topic: The Marvels of Monads
One veryveryvery simplistic way of looking at this whole "Monad" thing is to think of it as a box with a very basic interface:
Return
Bind
Zero (optional)
The uses are similarly simple in concept - let's say you have a "thing":
You can wrap your "thing" in the box (this would be the "return") and have a "BoxOfThing"
You can give instructions on how to take the thing out of this box and put it into another box (Bind)
You can get an empty box (the "Zero": think of it as a sort of "no-op", like multiplying by one or adding zero)
(there are other rules, but these three are the most interesting)
The Bind bit is the really interesting part, and also the part that makes most people's heads explode; basically,
you're giving a specification of sorts for how to chain boxes together: Let's take a fairly simple Monad, the "Option"
or "Maybe" - a bit like Nullable<T>, but way cooler.
So everybody hates checking for null everywhere, but we're forced to due to the way reference types work; what we'd love
is to be able to code something like this:
var zipcodesNearby = order.Customer.Address.City.ZipCodes;
And either get back a valid answer if (customer is valid + address is valid + ...), or "Nothing" if any bit of that logic fails...but
no, we need to:
List<string> zipcodesNearBy = new List<string>();
if(goodOrder.Customer != null)
{
if(goodOrder.Customer.Address != null)
{
if(goodOrder.Customer.Address.City != null)
{
if(goodOrder.Customer.Address.City.ZipCodes != null)
{
zipcodesNearBy = goodOrder.Customer.Address.City.ZipCodes;
}
else { /* do something else? throw? */ }
}
else { /* do something else? throw? */ }
}
else { /* do something else? throw? */ }
}
else { /* do something else? throw? */ }
(note: you can also rely on null coalescing, when applicable - although it's pretty nasty looking)
List<string> nullCoalescingZips =
((((goodOrder ?? new Order())
.Customer ?? new Person())
.Address ?? new Address())
.City ?? new City())
.ZipCodes ?? new List<string>();
The Maybe monad "rules" might look a bit like:
(note:C# is NOT ideal for this type of Type-mangling, so it gets a bit wonky)
public static Maybe<T> Return(T value)
{
return ReferenceEquals(value, null) ? Maybe<T>.Nothing : new Maybe<T>() { Value = value };
}
public static Maybe<U> Bind<U>(Maybe<T> me, Func<T, Maybe<U>> map)
{
return me != Maybe<T>.Nothing ?
// extract, map, and rebox
map(me.Value) :
// We have nothing, so we pass along nothing...
Maybe<U>.Nothing;
}
But this leads to some NASTY code:
var result1 =
Maybe<string>.Bind(Maybe<string>.Return("hello"), hello =>
Maybe<string>.Bind(Maybe<string>.Return((string)null), doh =>
Maybe<string>.Bind(Maybe<string>.Return("world"), world =>
hello + doh + world).Value
).Value
);
Luckily, there's a neat shortcut: SelectMany is very roughly equivalent to "Bind":
If we implement SelectMany for our Maybe<T>...
public class Maybe<T>
{
public static readonly Maybe<T> Nothing = new Maybe<T>();
private Maybe() {}
public T Value { get; private set;}
public Maybe(T value) { Value = value; }
}
public static class MaybeExt
{
public static bool IsNothing<T>(this Maybe<T> me)
{
return me == Maybe<T>.Nothing;
}
public static Maybe<T> May<T>(this T value)
{
return ReferenceEquals(value, null) ? Maybe<T>.Nothing : new Maybe<T>(value);
}
// Note: this is basically just "Bind"
public static Maybe<U> SelectMany<T,U>(this Maybe<T> me, Func<T, Maybe<U>> map)
{
return me != Maybe<T>.Nothing ?
// extract, map, and rebox
map(me.Value) :
// We have nothing, so we pass along nothing...
Maybe<U>.Nothing;
}
// This overload is the one that "turns on" query comprehension syntax...
public static Maybe<V> SelectMany<T,U,V>(this Maybe<T> me, Func<T, Maybe<U>> map, Func<T,U,V> selector)
{
return me.SelectMany(x => map(x).SelectMany(y => selector(x,y).May()));
}
}
Now we can piggyback on LINQ comprehension syntax!
var result1 =
from hello in "Hello".May()
from oops in ((string)null).May()
from world in "world".May()
select hello + oops + world;
// prints "Was Nothing!"
Console.WriteLine(result1.IsNothing() ? "Was Nothing!" : result1.Value);
var result2 =
from hello in "Hello".May()
from space in " ".May()
from world in "world".May()
select hello + space + world;
// prints "Hello world"
Console.WriteLine(result2.IsNothing() ? "Was Nothing!" : result2.Value);
var goodOrder = new Order { Customer = new Person { Address = new Address { City = new City { ZipCodes = new List<string>{"90210"}}}}};
var badOrder = new Order { Customer = new Person { Address = null }};
var zipcodesNearby =
from ord in goodOrder.May()
from cust in ord.Customer.May()
from add in cust.Address.May()
from city in add.City.May()
from zip in city.ZipCodes.May()
select zip;
// prints "90210"
Console.WriteLine(zipcodesNearby.IsNothing() ? "Nothing!" : zipcodesNearby.Value.FirstOrDefault());
var badZipcodesNearby =
from ord in badOrder.May()
from cust in ord.Customer.May()
from add in cust.Address.May()
from city in add.City.May()
from zip in city.ZipCodes.May()
select zip;
// prints "Nothing!"
Console.WriteLine(badZipcodesNearby.IsNothing() ? "Nothing!" : badZipcodesNearby.Value.FirstOrDefault());
Hah, just realized I forgot to mention the whole point of this...so basically, once you've figured out what the equivalent for "bind" is at each stage of your pipeline, you can use the same type of pseudomonadic code to handle the wrapping, unwrapping, and processing of each of your type transformations.
This won't answer your question, but a great place to look for inspiration on pipelines in the .NET world is PowerShell. They've implemented the pipeline model in a very clever way, and the objects flowing the pipeline will change all the time.
I've had to produce a Database to PDF document creation pipeline in the past and did it as PowerShell commandlets. It was so extensible that years later it is still being actively used and developed, it only migrated from PowerShell 1 to 2 and now possibly to 3.
You can get great ideas here: http://blogs.technet.com/b/heyscriptingguy/
A discussion has come up at work:
We've got a class that has an IList. Fact is an abstract base class, and there are several concrete subclasses (PopulationFact, GdpFact, etc).
Originally we'd query for a given fact in this fashion, i.e. by type:
.Facts.FirstOrDefault(x => x.Year == 2011 && x is GdpFact)
Now, however, the question's been raised whether we should introduce a FactType enum instead to do
.Facts.FirstOrDefault(x => x.Year == 2011 && x.FactType == FactType.Gdp)
The suggestion has been raised because it is supposedly faster. I'll admit that I've not written any tests to try and discern the difference in performance, but I have two questions:
1) Is 'querying on type' like this inherently bad?
2) Isn't adding a FactType enum just superfluous anyway, given that facts are strongly typed?
UPDATE
To clarify, this is LINQ to objects and GdpFact:Fact.
UPDATE 2
We've measured using current typical data (4 facts) and the results are in:
lookup on enum: 0.29660000000000003 milliseconds
lookup on type: 0.24530000000000002 milliseconds
So type lookup is faster in this context! I will choose my accepted answer carefully.
I've done a test, my results for 1000000 iterations are approximately
ByCast 166ms
ByType 84ms
ByEnum 98ms
So the enum is in fact superfluous and slower but, not by much. This should not be too suprising, the type system is fundamental to the .Net Framework.
Test code transcribed below, apologies for errata
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
class Program
{
private enum TypeOfFact
{
Gdp,
Other
}
private abstract class Fact
{
public virtual int Year { get; set; }
public abstract TypeOfFact FactType { get; }
}
private class GdpFact : Fact
{
public override TypeOfFact FactType
{
get { return TypeOfFact.Gdp; }
}
}
private class OtherFact : Fact
{
public override TypeOfFact FactType
{
get { return TypeOfFact.Other; }
}
}
static void Main()
{
Ilist<Fact> facts = new List<Fact>
{
new GdpFact { Year = 2010 },
new OtherFact { Year = 2010 },
new GdpFact { Year = 2009 },
new OtherFact { Year = 2009 },
new GdpFact { Year = 2011 },
new OtherFact { Year = 2011 },
};
const int interations = 1000000;
var funcs = new List<Func<IList<Fact>, Fact>>
{
ByList,
ByType,
ByEnum
};
// Warmup
foreach (var func in funcs)
{
Measure(5, func, facts);
}
// Results
foreach (var result in funcs.Select(f => new
{
Description = f.Method.Name,
Ms = Measure(iterations, f, facts)
}))
{
Console.WriteLine(
"{0} time = {1}ms",
result.Description,
result.Ms);
}
}
private static long Measure(
int iterations,
Func<IList<Fact>, Fact> func,
IList<Fact> facts)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < iterations; i++)
{
func.Invoke(facts);
}
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
private static Fact ByType(IList<Fact> facts)
{
return facts.FirstOrDefault(f =>
f.Year == 2011 && f is GdpFact);
}
private static Fact ByEnum(IList<Fact> facts)
{
return facts.FirstOrDefault(f =>
f.Year == 2011 && f.FactType == TypeOfFact.Gdp);
}
private static Fact ByCast(IList<Fact> facts)
{
return facts.OfType<GdpFact>()
.FirstOrDefault(f => f.Year == 2011);
}
}
This question seems relevant.
All type of preformance related questions are strictly dependent on concrete application context, so may be answer(s) provided here will be partially right/wrong for your concrete case.
Having this in mind:
Checking enum value should be reasonably faster then checking for type, as in first case, you just check for equality 2 integers (enum values).
But that introduce one more field in the object, that has to be tracked to have correct value (unit test), which you do not need in second case, as CLR cares about correct type initialization.
I think it's better that you profile your ideas against relevant amount of data, that your app usually operates over, and will come out with correct idea for you.
I think your original approach is fine. the 'is' keyword is provided for this purpose. MSDN does not discourage the use of 'is'. Using an enum seems to be over-engineering. We should try and keep code simple. Fewer the lines of code the better it is in most situations.
Is this maybe a solution looking for a problem?
I think having both concrete subtypes and an enum in the base type could potentially obfuscate your design. You could imagine someone coming along later and writing a new concrete class but not realising they needed to add to the enum as well...
Unless you find you have a specific problem to do with performance, I'd be tempted to prioritise clarity instead. Therefore if you need different concrete classes (and I'm assuming you do since that's how you've coded it to start with) then I'd stick with your types rather than move to an enum.
It's possible that checking an enum value will be faster than a runtime type-check, but...
It's a micro-optimisation -- you're unlikely to notice much perf difference in real-world scenarios.
It makes things more complicated, and more complicated means more likely to break.
For example, what's to stop you, or one of your colleagues, accidentally doing something like this?
public class PopulationFact : Fact
{
public FactType FactType = FactType.GdpFact; // should be PopulationFact
}
I'd stick with the type-check. There's actually a built-in LINQ method that'll do it for you:
.Facts.FirstOrDefault(x => x.Year == 2011).OfType<GdpFact>()
I have seen the Tuple introduced in .Net 4 but I am not able to imagine where it can be used. We can always make a Custom class or Struct.
That's the point - it is more convenient not to make a custom class or struct all the time. It is an improvement like Action or Func... you can make this types yourself, but it's convenient that they exist in the framework.
With tuples you could easily implement a two-dimensional dictionary (or n-dimensional for that matter). For example, you could use such a dictionary to implement a currency exchange mapping:
var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);
decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58
There's an excellent article in MSDN magazine that talks about the belly-aching and design considerations that went into adding Tuple to the BCL. Choosing between a value type and a reference type is particularly interesting.
As the article makes clear, the driving force behind Tuple was so many groups inside of Microsoft having a use for it, the F# team up front. Although not mentioned, I reckon that the new "dynamic" keyword in C# (and VB.NET) had something to do with it as well, tuples are very common in dynamic languages.
It is otherwise not particularly superior to creating your own poco, at least you can give the members a better name.
UPDATE: due for a big revision in C# version 7, now getting a lot more syntax love. Preliminary announcement in this blog post.
Here's a small example - say you have a method that needs to lookup a user's handle and email address, given a user Id. You can always make a custom class that contains that data, or use a ref / out parameter for that data, or you can just return a Tuple and have a nice method signature without having to create a new POCO.
public static void Main(string[] args)
{
int userId = 0;
Tuple<string, string> userData = GetUserData(userId);
}
public static Tuple<string, string> GetUserData(int userId)
{
return new Tuple<string, string>("Hello", "World");
}
I used a tuple to solve Problem 11 of Project Euler:
class Grid
{
public static int[,] Cells = { { 08, 02, 22, // whole grid omitted
public static IEnumerable<Tuple<int, int, int, int>> ToList()
{
// code converts grid to enumeration every possible set of 4 per rules
// code omitted
}
}
Now I can solve the whole problem with:
class Program
{
static void Main(string[] args)
{
int product = Grid.ToList().Max(t => t.Item1 * t.Item2 * t.Item3 * t.Item4);
Console.WriteLine("Maximum product is {0}", product);
}
}
I could have used a custom type for this, but it would have looked exactly like Tuple.
C#'s tuple syntax is ridiculously bulky, so tuples are painful to declare. And it doesn't have pattern matching, so they're also painful to use.
But occasionally, you just want an ad-hoc grouping of objects without creating a class for it. For example, let's say I wanted to aggregate a list, but I wanted two values instead of one:
// sum and sum of squares at the same time
var x =
Enumerable.Range(1, 100)
.Aggregate((acc, x) => Tuple.Create(acc.Item1 + x, acc.Item2 + x * x));
Instead of combining a collection of values into a single result, let's expand a single result into a collection of values. The easiest way to write this function is:
static IEnumerable<T> Unfold<T, State>(State seed, Func<State, Tuple<T, State>> f)
{
Tuple<T, State> res;
while ((res = f(seed)) != null)
{
yield return res.Item1;
seed = res.Item2;
}
}
f converts some state into a tuple. We return the first value from the tuple and set our new state to the second value. This allows us to retain state throughout the computation.
You use it as such:
// return 0, 2, 3, 6, 8
var evens =
Unfold(0, state => state < 10 ? Tuple.Create(state, state + 2) : null)
.ToList();
// returns 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
var fibs =
Unfold(Tuple.Create(0, 1), state => Tuple.Create(state.Item1, Tuple.Create(state.Item2, state.Item1 + state.Item2)))
.Take(10).ToList();
evens is fairly straightforward, but fibs is a little more clever. Its state is actually a tuple which holds fib(n-2) and fib(n-1) respectively.
I don't like the abuse of them, since they produce code that doesn't explain itself, but they're awesome to implement on-the-fly compound keys, since they implement IStructuralEquatable and IStructuralComparable (to use both for lookup and ordering purposes).
And they combine all of their items' hashcodes, internally; for example, here is Tuple's GetHashCode (taken from ILSpy):
int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
{
return Tuple.CombineHashCodes(comparer.GetHashCode(this.m_Item1), comparer.GetHashCode(this.m_Item2), comparer.GetHashCode(this.m_Item3));
}
Tuples are great for doing multiple async IO operations at a time and returning all the values together. Here is the examples of doing it with and without Tuple. Tuples can actually make your code clearer!
Without (nasty nesting!):
Task.Factory.StartNew(() => data.RetrieveServerNames())
.ContinueWith(antecedent1 =>
{
if (!antecedent1.IsFaulted)
{
ServerNames = KeepExistingFilter(ServerNames, antecedent1.Result);
Task.Factory.StartNew(() => data.RetrieveLogNames())
.ContinueWith(antecedent2 =>
{
if (antecedent2.IsFaulted)
{
LogNames = KeepExistingFilter(LogNames, antecedent2.Result);
Task.Factory.StartNew(() => data.RetrieveEntryTypes())
.ContinueWith(antecedent3 =>
{
if (!antecedent3.IsFaulted)
{
EntryTypes = KeepExistingFilter(EntryTypes, antecedent3.Result);
}
});
}
});
}
});
With Tuple
Task.Factory.StartNew(() =>
{
List<string> serverNames = data.RetrieveServerNames();
List<string> logNames = data.RetrieveLogNames();
List<string> entryTypes = data.RetrieveEntryTypes();
return Tuple.Create(serverNames, logNames, entryTypes);
}).ContinueWith(antecedent =>
{
if (!antecedent.IsFaulted)
{
ServerNames = KeepExistingFilter(ServerNames, antecedent.Result.Item1);
LogNames = KeepExistingFilter(LogNames, antecedent.Result.Item2);
EntryTypes = KeepExistingFilter(EntryTypes, antecedent.Result.Item3);
}
});
If you were using an anonymous function with an implied type anyway then you aren't making the code less clear by using the Tuple. Retuning a Tuple from a method? Use sparingly when code clarity is key, in my humble opinion. I know functional programming in C# is hard to resist, but we have to consider all of those old clunky "object oriented" C# programmers.
Tuples are heavily used in functional languages which can do more things with them, now F# is a 'official' .net language you may want to interoperate with it from C# and pass them between code written in two languages.
I tend to avoid Tuple for most scenarios since it hurts readability. However, Tuple is useful when you need to group unrelated data.
For example, suppose you have a list of cars and the cities in which they were purchased:
Mercedes, Seattle
Mustang, Denver
Mercedes, Seattle
Porsche, Seattle
Tesla, Seattle
Mercedes, Seattle
You want to aggregate the counts for each car per city:
Mercedes, Seattle [3]
Mustang, Denver [1]
Porsche, Seattle [1]
Tesla, Seattle [1]
To do this, you create a Dictionary. You have a few options:
Create a Dictionary<string, Dictionary<string, int>>.
Create a Dictionary<CarAndCity, int>.
Create a Dictionary<Tuple<string, string>, int>.
Readability is lost with the first option. It will require you to write a lot more code.
The second option works and is succinct, but car and city aren't really related and probably don't belong in a class together.
The third option is succinct and clean. It's a good use of Tuple.
A few examples off the top of my head:
An X and Y location (and Z if you like)
a Width and Height
Anything measured over time
For example you wouldn't want to include System.Drawing in a web application just to use Point/PointF and Size/SizeF.
You should be very careful with using Tuple and probably think twice before do this. From my previous experience I found out that using Tuple makes code very difficult to read and support in the future. A while ago, I had to fix some code where tuples were used almost everywhere. Instead of thinking about proper object models, they just used tuples. That was nightmare... sometimes I wanted to kill the guy who wrote the code...
Don't want to say that you shouldn't use Tuple and it's evil or something and I'm hundred percent sure there are some tasks where the Tuple is the best candidate to be used, but probably you should think again, do you REALLY need it?
The best use for Tuples I have found is when needing to return more than 1 type of object from a method, you know what object types and number they will be, and it is not a long list.
Other simple alternatives would be using an 'out' parameter
private string MyMethod(out object)
or making a Dictionary
Dictionary<objectType1, objectType2>
Using a Tuple however saves either creating the 'out' object or having to essentially look-up the entry in the dictionary;
Just found the solution of one of my issues in Tuple. It is like declaring a class in scope of a method, but with lazy declaration of its fields names. You operate with collections of tuples, its single instances and then create a collection of anonymous type with the required field names, basing on your tuple. This avoids you from creating the new class for this purpose.
The task is to write a JSON response from LINQ without any additional classes:
//I select some roles from my ORM my with subrequest and save results to Tuple list
var rolesWithUsers = (from role in roles
select new Tuple<string, int, int>(
role.RoleName,
role.RoleId,
usersInRoles.Where(ur => ur.RoleId == role.RoleId).Count()
));
//Then I add some new element required element to this collection
var tempResult = rolesWithUsers.ToList();
tempResult.Add(new Tuple<string, int, int>(
"Empty",
-1,
emptyRoleUsers.Count()
));
//And create a new anonimous class collection, based on my Tuple list
tempResult.Select(item => new
{
GroupName = item.Item1,
GroupId = item.Item2,
Count = item.Item3
});
//And return it in JSON
return new JavaScriptSerializer().Serialize(rolesWithUsers);
Of cause we could do this with declaring a new Class for my groups, but the idea to create such an anonimous collections without declaring of new classes.
Well in my case, I had to use a Tuple when I found out that we cannot use out parameter in an asynchronous method. Read about it here. I also needed a different return type. So I used a Tuple instead as my return type and marked the method as async.
Sample code below.
...
...
// calling code.
var userDetails = await GetUserDetails(userId);
Console.WriteLine("Username : {0}", userDetails.Item1);
Console.WriteLine("User Region Id : {0}", userDetails.Item2);
...
...
private async Tuple<string,int> GetUserDetails(int userId)
{
return new Tuple<string,int>("Amogh",105);
// Note that I can also use the existing helper method (Tuple.Create).
}
Read more about Tuple here.
Hope this helps.
Changing shapes of objects when you need to send them across wire or pass to different layer of application and multiple objects get merged into one:
Example:
var customerDetails = new Tuple<Customer, List<Address>>(mainCustomer, new List<Address> {mainCustomerAddress}).ToCustomerDetails();
ExtensionMethod:
public static CustomerDetails ToCustomerDetails(this Tuple<Website.Customer, List<Website.Address>> customerAndAddress)
{
var mainAddress = customerAndAddress.Item2 != null ? customerAndAddress.Item2.SingleOrDefault(o => o.Type == "Main") : null;
var customerDetails = new CustomerDetails
{
FirstName = customerAndAddress.Item1.Name,
LastName = customerAndAddress.Item1.Surname,
Title = customerAndAddress.Item1.Title,
Dob = customerAndAddress.Item1.Dob,
EmailAddress = customerAndAddress.Item1.Email,
Gender = customerAndAddress.Item1.Gender,
PrimaryPhoneNo = string.Format("{0}", customerAndAddress.Item1.Phone)
};
if (mainAddress != null)
{
customerDetails.AddressLine1 =
!string.IsNullOrWhiteSpace(mainAddress.HouseName)
? mainAddress.HouseName
: mainAddress.HouseNumber;
customerDetails.AddressLine2 =
!string.IsNullOrWhiteSpace(mainAddress.Street)
? mainAddress.Street
: null;
customerDetails.AddressLine3 =
!string.IsNullOrWhiteSpace(mainAddress.Town) ? mainAddress.Town : null;
customerDetails.AddressLine4 =
!string.IsNullOrWhiteSpace(mainAddress.County)
? mainAddress.County
: null;
customerDetails.PostCode = mainAddress.PostCode;
}
...
return customerDetails;
}
An out parameter is great when there are only a few values that need to be returned,
but when you start encountering 4, 5, 6, or more values that need to be returned, it
can get unwieldy. Another option for returning multiple values is to create and return
a user-defined class/structure or to use a Tuple to package up all the values that need
to be returned by a method.
The first option, using a class/structure to return the values, is straightforward. Just
create the type (in this example it is a structure) like so:
public struct Dimensions
{
public int Height;
public int Width;
public int Depth;
}
The second option, using a Tuple, is an even more elegant solution than using a userdefined
object. A Tuple can be created to hold any number of values of varying types.
In addition, the data you store in the Tuple is immutable; once you add the data to
the Tuple through the constructor or the static Create method, that data cannot be
changed.
Tuples can accept up to and including eight separate values. If you need to return
more than eight values, you will need to use the special Tuple class:
Tuple Class
When creating a Tuple with more than eight values, you cannot use the static Create
method—you must instead use the constructor of the class. This is how you would
create a Tuple of 10 integer values:
var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> (
1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));
Of course, you can continue to add more Tuples to the end of each embedded Tuple,
creating any size Tuple that you need.
Only for prototyping - Tuples are meaningless. It convenient to use them but it's a shortcut only! For prototypes - fine. Just be sure to delete this code later.
It easy to write, hard to read. It has no visible advantages over classes, inner classes , anonymous classes etc.
Well I tried 3 ways to solve the same problem in C#7 and I have found a use case for Tuples.
Working with dynamic data in web projects can sometimes be a pain when mapping etc.
I like the way the Tuple just auto mapped onto item1, item2, itemN which seems more robust to me than using array indexes where you might get caught on an out of index item or using the anonymous type where you may misspell a property name.
It feels like a DTO has been created for free just by using a Tuple and I can access all the properties using itemN which feels more like static typing without having to create a separate DTO for that purpose.
using System;
namespace Playground
{
class Program
{
static void Main(string[] args)
{
var tuple = GetTuple();
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
Console.WriteLine(tuple.Item3);
Console.WriteLine(tuple);
Console.WriteLine("---");
var dyn = GetDynamic();
Console.WriteLine(dyn.First);
Console.WriteLine(dyn.Last);
Console.WriteLine(dyn.Age);
Console.WriteLine(dyn);
Console.WriteLine("---");
var arr = GetArray();
Console.WriteLine(arr[0]);
Console.WriteLine(arr[1]);
Console.WriteLine(arr[2]);
Console.WriteLine(arr);
Console.Read();
(string, string, int) GetTuple()
{
return ("John", "Connor", 1);
}
dynamic GetDynamic()
{
return new { First = "John", Last = "Connor", Age = 1 };
}
dynamic[] GetArray()
{
return new dynamic[] { "John", "Connor", 1 };
}
}
}
}