22

What's the best way to create a list with an arbitrary number of instances of the same object? i.e is there a more compact or efficient way to do the following?

static List<MyObj> MyObjs = Enumerable.Range(0, 100)
    .Select(i => new MyObj())
    .ToList();

(Enumerable.Repeat would give me ten references to the same object, so I don't think it would work.)

Jonathan Nixon
  • 4,940
  • 5
  • 39
  • 53
Arithmomaniac
  • 4,604
  • 3
  • 38
  • 58

5 Answers5

10

Edited to reflect that this method does not work.

I was curious about your comment about Enumerable.Repeat, so I tried it.

//do not use!
List<object> myList = Enumerable.Repeat(new object(), 100).ToList();

I confirmed that they do all share the same reference like the OP mentioned.

Gray
  • 7,050
  • 2
  • 29
  • 52
  • 2
    You are overwriting the instances when you do `myList[i] = ...`. Make a class Foo, with a property Bar, then change Bar, you will see that it changes for every index. – cadrell0 Jul 25 '13 at 18:05
  • @cadrell0 thank you for your comment. You are correct - I threw myself off there. I'll leave my answer here merely as a lesson for any one else on what not to do. – Gray Jul 25 '13 at 18:08
  • But a select() added let's you create new objects for every element. – HankTheTank Oct 04 '21 at 11:21
9

This wouldn't be hard to implement as an iterator:

IEnumerable<T> CreateItems<T> (int count) where T : new() {
    return CreateItems(count, () => new T());
}

IEnumerable<T> CreateItems<T> (int count, Func<T> creator) {
    for (int i = 0; i < count; i++) {
        yield return creator();
    }
}
Josh
  • 632
  • 7
  • 13
4

Apparently, the answer is "no". Thanks, everyone!

Arithmomaniac
  • 4,604
  • 3
  • 38
  • 58
2

Not sure what is wrong with a for loop in this case. At the very least, we can presize the capacity of the list. That might not be important for 100 objects, but the size is arbitrary.

public class MyClass
{
    static int Capacity = 100;
    static List<MyObj> MyObjs = new List<MyObj>(Capacity);

    static MyClass() {
       for( var i = 0; i < Capacity; i++ ) {
          MyObjs.Add(new MyObj());
       }
    }
}
B2K
  • 2,541
  • 1
  • 22
  • 34
  • 1
    Using `ToList` uses the `List(IEnumerable items)` constructor of `List`, which I believe is a little faster than calling `Add` for each item. It is at the very least the same. `Range` uses a `for` loop internally. Your solution is at best, the same performance, but more code. – cadrell0 Jul 25 '13 at 18:10
  • The .Select method must necessarily create a new Enumeration since the type has changed from an int to a MyObj. It must also invoke an anonymous method 100 times and finally convert this all into a List. – B2K Jul 25 '13 at 18:39
  • 1
    http://msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx Both Range and Select use `yield return`. Because execution is deferred to the point that `ToList` is called, the results are only enumerated once, which happens while building the list. – cadrell0 Jul 25 '13 at 18:44
  • But simply deferring the execution doesn't prevent the execution. The final ToList call will still trigger 100 calls to Func(i) { return new MyObj() }. Granted, it's done efficiently, but it HAS to have some additional overhead. Still, if I were coding this, and if the capacity was small, I would opt for the one-liner as well. – B2K Jul 25 '13 at 19:07
1

you can use the enumerable as a base and use select to create the new objects:

List<object> myList = Enumerable.Repeat(null, 100).Select(_ => new object()).ToList();

you can attach a debugger, the new is executed every time. This is almost the same as your code, but you don't have to provide a fake range. You only provide a null as fake object you want to repeat...

HankTheTank
  • 537
  • 5
  • 15