views:

327

answers:

9

When I have to write methods which return two values, I usually go about it as in the following code which returns a List<string>. Or if I have to return e.g. a id and string, then I return a List<object> and then pick them out with index number and recast the values.

This recasting and referencing by index seems inelegant so I want to develop a new habit for methods that return two values. What is the best pattern for this?

using System;
using System.Collections.Generic;
using System.Linq;

namespace MultipleReturns
{
    class Program
    {
        static void Main(string[] args)
        {
            string extension = "txt";

            {
                List<string> entries = GetIdCodeAndFileName("first.txt", extension);
                Console.WriteLine("{0}, {1}", entries[0], entries[1]);
            }

            {
                List<string> entries = GetIdCodeAndFileName("first", extension);
                Console.WriteLine("{0}, {1}", entries[0], entries[1]);
            }

            Console.ReadLine();
        }

        /// <summary>
        /// gets "first.txt", "txt" and returns "first", "first.txt"
        /// gets "first", "txt" and returns "first", "first.txt"
        /// it is assumed that extensions will always match
        /// </summary>
        /// <param name="line"></param>
        public static List<string> GetIdCodeAndFileName(string line, string extension)
        {
            if (line.Contains("."))
            {
                List<string> parts = line.BreakIntoParts(".");
                List<string> returnItems = new List<string>();
                returnItems.Add(parts[0]);
                returnItems.Add(line);
                return returnItems;
            }
            else
            {
                List<string> returnItems = new List<string>();
                returnItems.Add(line);
                returnItems.Add(line + "." + extension);
                return returnItems;
            }
        }

    }

    public static class StringHelpers
    {
        public static List<string> BreakIntoParts(this string line, string separator)
        {
            if (String.IsNullOrEmpty(line))
                return null;
            else
            {
                return line.Split(new string[] { separator }, StringSplitOptions.None).Select(p => p.Trim()).ToList();
            }
        }
    }
}

Added:

Ok, thanks everyone, I like the "return a custom class" answers best, never really thought out was that easy to read, seems like a hack to me returning the first variable one way and the second another, here is my refactoring returning a custom class:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MultipleReturns
{
    class Program
    {
        static void Main(string[] args)
        {
            string extension = "txt";

            {
                IdCodeFileNamePair pair = GetIdCodeAndFileName("first.txt", extension);
                Console.WriteLine("{0}, {1}", pair.IdCode, pair.FileName);
            }

            {
                IdCodeFileNamePair pair = GetIdCodeAndFileName("first", extension);
                Console.WriteLine("{0}, {1}", pair.IdCode, pair.FileName);
            }

            Console.ReadLine();
        }

        /// <summary>
        /// gets "first.txt", "txt" and returns "first", "first.txt"
        /// gets "first", "txt" and returns "first", "first.txt"
        /// it is assumed that extensions will always match
        /// </summary>
        /// <param name="line"></param>
        public static IdCodeFileNamePair GetIdCodeAndFileName(string line, string extension)
        {
            if (line.Contains("."))
            {
                List<string> parts = line.BreakIntoParts(".");
                List<string> returnItems = new List<string>();
                return new IdCodeFileNamePair { IdCode = parts[0], FileName = line };
            }
            else
            {
                List<string> returnItems = new List<string>();
                return new IdCodeFileNamePair { IdCode = line, FileName = line + "." + extension };
            }
        }

    }

    public static class StringHelpers
    {
        public static List<string> BreakIntoParts(this string line, string separator)
        {
            if (String.IsNullOrEmpty(line))
                return null;
            else
            {
                return line.Split(new string[] { separator }, StringSplitOptions.None).Select(p => p.Trim()).ToList();
            }
        }
    }

    public class IdCodeFileNamePair
    {
        public string IdCode { get; set; }
        public string FileName { get; set; }
    }
}
+2  A: 

Use keyword out

http://msdn.microsoft.com/en-us/library/ee332485.aspx

This is way better than casting specific elements of a list of objects.

Eric Mickelsen
How do you pass the result neatly into another method call?
John
`int a; Foo(out a); Bar(a);` In short, you don't. `out` is not elegant, but sometimes necessary.
Matt Greer
@John: The filename should be a separate parameter to any subsequent method calls anyway. This is just as neat as anything else.
Eric Mickelsen
Thanks, Matt. A more complicated example would be `int a; int b = Foo(out a); Bar(a, b);` If you want tight functional code, use F# instead.
Eric Mickelsen
Using out is much cleaner code, as we have found. We used to use structs or classes to pass return variables around and it ended up being a mess as we had all these classes laying around that were only used once. Using out instead proved to be MUCH cleaner looking and functioning.
TheCodeMonk
+1 interesting point about classes (and hence dependencies) lying around, will take that into account in larger projects
Edward Tanguay
A: 

How about a template Pair<T,V>? Of course you can construct custom data-types for this if the same ones will be passed around, but it's not as generic.

(Or perhaps does C# let you generate classes on-the-fly? update: ok ignore this bit)

John
Yes you can generate a class on the fly, but it must remain in the local scope, so it can not be the return value of a method.
Matt Greer
+8  A: 

You could return a tuple, starting with 4.0.

mwilson
Was making this community wiki accidental? You don't accrue rep for CW posts!
Martin Smith
+1  A: 

Why not public static void GetIdCodeAndFileName(string line, string extension, out string id, out string fileName)?

Yuriy Faktorovich
How do you pass the result neatly into another method call?
John
@John: The filename should be a separate parameter to any subsequent method calls anyway. This is just as neat as anything else.
Eric Mickelsen
In this example maybe, wasn't the question more general?
John
+12  A: 

I prefer either to create a lightweight class with two properties (see below), or to use a tuple (now available baked into the framework in .NET 4 but not hard to write your own)

class MyReturnValue
{
    public string Id { get; set; }
    public string Name { get; set; }
}
Mark Heath
A: 

There just plain isn't an elegant way to return two values in C#. Although I definitely think using out parameters is better than returning a List, that's not very maintainable or readable. C# developers expect out, it's supported directly by the language, and it's well understood what it's doing.

Starting in 4.0, you can use a Tuple instead.

Matt Greer
+1  A: 

I either use out params, or create a struct with properties (for the property initializer syntax) and return that. Creating a custom struct/class has the advantage of variable naming that can match the data being passed. This makes the code more readable.

IdAndString GetIDAndString()
{
    return new IdAndString()
    {
        ID = 1,
        Str = "123"
    };
}

struct IdAndString
{
    public int ID { get; set; }
    public string Str { get; set; } 
}
Jon Benedicto
+2  A: 

Another option is to return a KeyValuePair<int, string>.

Anna Lear
+1  A: 

I would recommend using of a Light weight object like Mark suggested. But there are other patterns as well.

Another simple approach is, use the call by reference property. Like, the caller sends an empty array as a parameter, which will be populated by the function.

Phanindra