4

I have an object with a dynamic array of strings which I've implemented as follows:

public class MyThing { 
    public int NumberOfThings { get; set; }
    public string _BaseName { get; set; }
    public string[] DynamicStringArray {
        get {
            List<string> dsa = new List<string>();
            for (int i = 1; i <= this.NumberOfThings; i++) {
                dsa.Add(string.Format(this._BaseName, i));
            }
            return dsa.ToArray();
        }
    }
}

I was trying to be a little cooler earlier and implement something that autocreated the formatted list of arrays in LINQ but I've managed to fail.

As an example of the thing I was trying:

int i = 1;
// create a list with a capacity of NumberOfThings
return new List<string>(this.NumberOfThings)
    // create each of the things in the array dynamically
    .Select(x => string.Format(this._BaseName, i++))
    .ToArray();

It's really not terribly important in this case, and performance-wise it might actually be worse, but I was wondering if there was a cool way to build or emit an array in LINQ extensions.

Alex C
  • 16,624
  • 18
  • 66
  • 98
  • 7
    I say return `IEnumerable` then, not `string[]`. – GSerg Sep 14 '15 at 14:43
  • 3
    Emit a list... yet you use ToArray (not ToList()) and then cast to a List ... why not use ToList() then you won't need the cast (as you are constructing a string in the select)? – Paul Zahra Sep 14 '15 at 14:49
  • Hey Paul, apologies, I've cleaned up the references to "list" I misspoke. I actually *did* want an array, but erroneously used list and array as synonyms (even though I'm fully aware that they're different objects). Sorry about the confusion. Backs' answer was what I was looking for. – Alex C Sep 14 '15 at 22:21

3 Answers3

11

Will Range help?

return Enumerable
  .Range(1, this.NumberOfThings)
  .Select(x => string.Format(this._BaseName, x))
  .ToArray();
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
Backs
  • 24,430
  • 5
  • 58
  • 85
  • He wants a list, not an array. – iheanyi Sep 14 '15 at 14:55
  • 3
    @iheanyi really? in both examples i see `.ToArray()`. Anyway, just replace `.ToArray()` to `.ToList()` and you'll get it. Also, you can edit answers – Backs Sep 14 '15 at 14:56
  • Hmm, I can't make that edit - not enough text being changed. But you raise a good point regarding his examples. – iheanyi Sep 14 '15 at 15:02
  • @iheanyi - oops! I misspoke :) sorry for the confusion. I suppose I use List & Array interchangeably while talking to people but obviously use the right actual type when programming. Backs is correct, the final step of .ToList() or .ToArray() is trivial enough to be irrelevant while Enumerable.Range is the real "guts" of the answer. – Alex C Sep 14 '15 at 22:17
1

Your property could return an IEnumerable and you could then invoke the ToArray() extension on that, if you needed to.

public string[] DynamicStringArray
{
    get
    {
        for (int i=1; i <= this.NumberOfThings; i++)
            yield return string.Format(this._BaseName, i);
    }
}

However, yields are inherently slow because of the context switching that goes on. You're better off doing this:

public string[] DynamicStringArray
{
    get
    {
        string[] result = new string[this.NumberOfThings];
        for (int i = 0; i < this.NumberOfThings; i++)
        {
            result[i] = string.Format(this._BaseName, i + 1));
        }
        return result;
    }
}

Those Linq extension methods are nice for when you're feeling lazy. But if you need it to perform well you should avoid them.

Lorek
  • 855
  • 5
  • 11
  • 2
    "context switching" - can you explain what you are talking about? – Alexei Levenkov Sep 14 '15 at 14:58
  • Each yield return switches from managed to native then back to managed. You can put a breakpoint on the yield return and watch what happens in the call stack. – Lorek Sep 14 '15 at 15:00
  • @Lorek You might be not understanding it fully, please see e.g. http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx – GSerg Sep 14 '15 at 15:12
  • 1
    To my knowledge there is no manged-to-native-and-back transition when you call next on iterator. `Yield return` produces relatively simple managed code - the same as any other manged code. I'd recommend checking out http://stackoverflow.com/questions/742497/yield-statement-implementation and related links - it may clarify for you at least what is going on. If you still see native code somewhere - consider if asking question about it is useful for you. – Alexei Levenkov Sep 14 '15 at 15:14
  • Weird. I just tested it and it behaves as it should, which is not how I originally described. I can only guess that the compiler has changed since I last tested it (several years ago) because it used to switch context. The only context switching now is in managed code. And, although that is not as bad as what I thought was happening, it can still be avoided to improve performance. – Lorek Sep 14 '15 at 15:40
1

I'd rather redesign a bit the current solution:

  public class MyThing { 
    ...

    // Note IEnumerable<String> instead of String[]
    public IEnumerable<String> DynamicString(int numberOfThings) {
      if (numberOfThings < 0)
        throw new ArgumentOutOfRangeException("numberOfThings");

      for (int i = 0; i < numberOfThings; ++i)
        yield return string.Format(this._BaseName, i + 1);
    } 
  }

whenever you want, say, an array you can easily obtain it:

  MyThing thing = ...;
  // Add .ToArray() to have an array
  String[] myArray = thing.DynamicString(18).ToArray();

but whenever all you want is just loop there's no need to create an array or list (materialize a result)

  // no materialization: no array of 1000000 items
  foreach (String item in thing.DynamicString(1000000)) {
    ...
  }
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215