2

Suppose I have a function which populates a few lists like the following:

public static List<T> foo()
{
    List<T> bar = List<T>();
    List<T> buzz = List<T>();
    List<T> bazz = List<T>();
    foreach (T item in someList)  # Cannot change
    {
        if (condition1)
        {
            bar.add(item);
        }
        if (condition2)
        {
            buzz.add(item);
        }
        if (condition3)
        {
            bazz.add(item);
        }
    }
    return bar;
}

At the moment I only return one list, however I'm interested in returning all the lists since each one contains elements which must be handled differently by the calling function. What should I be returning here? I've considered a tuple of lists, or a list of tuples, but I would expect that there are other designs which would serve this pattern better. Note that unlike the question which was suggested as a duplicate, I'm looking for a solution for an arbitrary number of lists with names.

I can't separate the iteration into separate functions for each list, the actual iteration calls a command which isn't idempotent.

As a bonus, it would be great if the lists were named, so we wouldn't be relying on their position in some tuple or etc but could call them by "bar", "buzz", etc.

shayaan
  • 1,482
  • 1
  • 15
  • 32
  • 1
    It depends, tuples are a possibility https://learn.microsoft.com/de-de/dotnet/csharp/tuples. Note that you can name the fields: `(int count, string msg) foo() { return (count: 0, msg: "foo"); }` – Micha Wiedenmann Jul 16 '18 at 16:53
  • 1
    You could also pass the lists into the function as `out` parameters. Yet another option is to create a class that has properties for the lists and return an instance of that class. – itsme86 Jul 16 '18 at 16:54
  • 1
    Or you can create a dummy holder class with List properties and return the values. It is clean and re-usable. – SelvaS Jul 16 '18 at 16:54
  • You could return a `List`, each iteration is a separate list – maccettura Jul 16 '18 at 16:54
  • Depending on the version of C# you are using you can value tuples or the `Tuple` class or create a custom class with the lists for your return type. – juharr Jul 16 '18 at 16:54
  • This has "code smell" written all over it, if you'll pardon the mixed metaphor. What you have here is a method that is useful for three values and, since your callee is not idempotent, probably useful for an effect as well. Such functions are difficult to reason about and dangerous to call. I would attack the problem by making an idempotent version of the helper method, and then this method gets a lot easier. – Eric Lippert Jul 16 '18 at 17:11
  • Also, I don't understand why you are not simply returning a struct. You say you want a tuple that has named fields. C# has tuples with named fields; they're called structs. – Eric Lippert Jul 16 '18 at 17:12
  • Part of the reason this question is hard to answer is because you have given us none of the semantics of your operation; what are you actually trying to get out of this method? – Eric Lippert Jul 16 '18 at 17:13
  • The non-idempotent code is a 3rd party command which is called, it does a lot of other work in addition to this, and unfortunately there is no way to break apart the functionality of the command – shayaan Jul 16 '18 at 18:17
  • What's really happening is that this is calling a [Perforce](https://www.perforce.com/) unshelve operation and finding out which files are unshelved, unresolved, and locked through looking at the output – shayaan Jul 16 '18 at 18:21
  • 1
    Could you execute the command once and copy its output into a `List`? You can then iterate over that list as many times as you like without re-doing the command. – Eric Lippert Jul 16 '18 at 18:25
  • 1
    I would be inclined in your case though to make a new type called `UnshelveResults` and have your method return an object that describes the results of the operation. This is one of those cases where it is entirely reasonable to have a method that is useful for both its effects and its result, since *the result describes the effects*. And this gives you the freedom to change the implementation details of `UnshelveResults` as you see fit. – Eric Lippert Jul 16 '18 at 18:27
  • Storing in a `List` and processing afterwards makes more sense, I'm not totally sure of the advantages of a type `UnshelveResults` versus a named tuple `(bar: bar, buzz: buzz, bazz: bazz)`, introducing a new class seems like it might be slight overkill – shayaan Jul 16 '18 at 18:32

4 Answers4

1

You could use yield. In your caller method, do something like:

foreach(var list in foo())
{

}

Then your method would look like:

public static IEnumerable<List<T>> foo()
{
    List<T> bar = new List<T>();
    List<T> buzz = new List<T>();
    List<T> bazz = new List<T>();
    foreach (var item in someList)
    {
        if (condition1)
        {
            bar.Add(item);
        }
        if (condition2)
        {
            buzz.Add(item);
        }
        if (condition3)
        {
            bazz.Add(item);
        }
    }
    yield return bar;
    yield return buzz;
    yield return bazz;
}
eye_am_groot
  • 682
  • 6
  • 19
1

If you are using C# 7, you can write a tuple this way:

public static (List<T> bar, List<T> buzz, List<T> bazz) foo()
{
    List<T> bar = List<T>();
    List<T> buzz = List<T>();
    List<T> bazz = List<T>();
    foreach (T item in someList)  # Cannot change
    {
        if (condition1)
        {
            bar.add(item);
        }
        if (condition2)
        {
            buzz.add(item);
        }
        if (condition3)
        {
            bazz.add(item);
        }
    }
    return (bar: bar, buzz: buzz, bazz: bazz);
}

Alternatively, you can create a BarBuzzBazz class:

public class BarBuzzBazz {
     public List<T> Bar { get; }
     public List<T> Buzz { get; }
     public List<T> Bazz { get; }

     // constructor for initialising the properties
}

and return:

 return new BarBuzzBazz(bar, buzz, bazz);

A third way is to pass the lists as parameters and populate them in the method:

public static void foo(out List<T> bar, out List<T> buzz, out List<T> bazz) 
{
    bar = List<T>();
    buzz = List<T>();
    bazz = List<T>();
    foreach (T item in someList)  # Cannot change
    {
        if (condition1)
        {
            bar.add(item);
        }
        if (condition2)
        {
            buzz.add(item);
        }
        if (condition3)
        {
            bazz.add(item);
        }
    }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

You can do with Tuple

public static Tuple<List<T>, List<T>,List<T>> Method()
{
    List<T> list1 = List<T>();
    List<T> list2 = List<T>();
    List<T> list3 = List<T>();
    return Tuple.Create(list1, list2,list3); 
}

or with your custom class,

Create an object with properties for the list and return it:

public class MyType
{
    public List<T> Prop1 { get; set; }
    public List<T> Prop2 { get; set; }
    public List<T> Prop3 { get; set; }
}

public static MyType Method()
{
    return new MyType { Prop1 = list1, Prop2 = list2 ,Prop3 = list3};
}
Sajeetharan
  • 216,225
  • 63
  • 350
  • 396
-1

If there are only 3 lists, then why not pass those lists into the function:

void Foo(ICollection<T> one, ICollection<T> two, ICollection<T> three);

If there are more, you could use a dictionary:

void Foo(IDictionary<TIdent, ICollection<T>> things);

Or, as suggested, you could use a tuple

(ICollection<T> one, ICollection<T>two, ICollection<T> three) Foo();
Neil
  • 11,059
  • 3
  • 31
  • 56