ean5533
ean5533

Reputation: 8994

Elegantly passing lists and objects as params

In C#, one can use the params keyword to specify an arbitrary number of typed parameters to a method:

public void DoStuff(params Foo[] foos) {...}

public void OtherStuff {
    DoStuff(foo1);
    DoStuff(foo2, foo3);
}

If you already have a list of objects, you can turn it into an array to pass to this method:

DoStuff(fooList.ToArray());

However, is there any elegant way to mix-n-match? That is, to pass in multiple objects and lists of objects and have the results flattened into one list or array for you? Ideally, I would like to be able to call my method like this:

DoStuff(fooList, foo1, foo2, anotherFooList, ...);

As of right now, the only way I know how to do this is to pre-process everything into one list, and I don't know of any way to do this generically.

Edit: To be clear, I'm not married to the params keyword, it's just a related mechanism that helped me explain what I wanted to do. I'm quite happy with any solution that looks clean and flattens everything into a single list.

Upvotes: 16

Views: 35146

Answers (4)

konkked
konkked

Reputation: 3231

you could make separate methods to load the objects into the same collection, not that elegant but it will work, and the logic is really easy to follow, and not very hard to implement.

public class Flattener<T> : IEnumerable<T>
{
    private List<T> _collection = new List<T> ( );
    public void Add ( params T [ ] list )
    {
        _collection.AddRange ( list );
    }

    public void Add ( params IEnumerable<T> [ ] lists )
    {
        foreach ( var list in lists )
            _collection.AddRange ( list );
    }

    public T Result
    {
        get
        {
            return _collection.ToArray();
        }
    }

    public IEnumerator<T> GetEnumerator ( )
    {
        return _collection.GetEnumerator ( );
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ( )
    {
        return GetEnumerator ( );
    }
}


Flattener<Foo> foos = new Flattener();
foos.Add(fooList, fooList2, fooList3,...);
foos.Add(foo1,foo2,foo3,...);
DoStuff(foos.Result);

Upvotes: 1

devuxer
devuxer

Reputation: 42344

You can't quite do what you're trying to do, but with an extension method, you could get pretty close:

void Main()
{
    var singleFoo = new Foo();
    var multipleFoos = new[] { new Foo(), new Foo(), new Foo() };
    var count = DoStuffWithFoos(singleFoo.Listify(), multipleFoos).Count();
    Console.WriteLine("Total Foos: " + count.ToString());
}

public IEnumerable<Foo> DoStuffWithFoos(params IEnumerable<Foo>[] fooLists)
{
    return fooLists.SelectMany(fl => fl); // this flattens all your fooLists into
                                          // a single list of Foos
}

public class Foo { }

public static class ExtensionMethods
{
    public static IEnumerable<Foo> Listify(this Foo foo)
    {
        yield return foo;
    }
}

Upvotes: 2

Lee
Lee

Reputation: 144126

You could create a class with implict conversions to wrap a single element and a list:

public class ParamsWrapper<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> seq;

    public ParamsWrapper(IEnumerable<T> seq)
    {
        this.seq = seq;
    }

    public static implicit operator ParamsWrapper<T>(T instance)
    {
        return new ParamsWrapper<T>(new[] { instance });
    }

    public static implicit operator ParamsWrapper<T>(List<T> seq)
    {
        return new ParamsWrapper<T>(seq);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return this.seq.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

then you can change your DoStuff method to:

private static void DoStuff(params ParamsWrapper<Foo>[] foos)
{
    Foo[] all = foos.SelectMany(f => f).ToArray();
    //
}

Upvotes: 13

Alexei Levenkov
Alexei Levenkov

Reputation: 100527

You can use Enumerable.Concat to join multiple lists and items like:

 DoStuff(fooList
       .Concat(Enumerable.Repeat(foo1,1))
       .Concat(Enumerable.Repeat(foo2,1))
       .Concat(Enumerable.Repeat(anotherFooList))
       .ToArray();

Note: there are likely much more readable ways to achieve whatever you are trying to do. Even passing IEnumerable<Foo> is more readable.

Upvotes: 4

Related Questions