8

I have a class (Biome) which contains a 2D array of custom objects (Level). I frequently want to loop through all (or some) of the levels and perform some actions, e.g. set their colour property.

Rather than writing the same nested for-loop over and over again, can I make my own method which will let me do something like: ForEachLevel(SetColour())?. Where SetColour() would be a method in my Biome class which just sets the Level's colour property to some random value or using some other logic based on factors within the Biome?

So instead of:

for (int r = 0; d < Rows; r++)
{
    for (int c = 0; l < Cols; c++)
    {
        if (some logic here)
            Levels[r, c].Colour = Color.Red;
        else
            Levels[r, c].Colour = Color.Green;
    }
}

I could do something like:

ForEachLevel(SetColour(--what goes here?--));

void SetColour(Level level){
    if (some logic here)
        level.Colour = Color.Red;
    else
        level.Colour = Color.Green;
}

Or even better, I'd like to make something similar which only runs a function on, say, rows X through to Y.

As you can see from my example I don't even know how I'd get the context of each Level instance into the SetColour function.

I can keep copy/pasting my custom nested for-loops to achieve what I want, but I was hoping someone with more experience might be able to understand what I'm trying to do and point me to the right direction on how I can use better C# techniques.

Because I've been vague I understand if a specific answer cannot be given, but some key concepts for further research would be appreciated!! Thanks

EDIT2 actually that previous attempt doesnt work at all. I forgot I need to actually call ForEachLevel somewhere else. Still working on it.

private void SomeOtherMethod()
{
    // where I want to actually use the ForEachLevel()
    ForEachLevel(SetLevelColor(--Level??--, Color.red));
}
private void ForEachLevel(Action DoAThing)
{
    for (int d = 0; d < Depths; d++)
    {
        for (int l = 0; l < Lanes; l++)
        {
            DoAThing();
        }
    }
}
private void SetLevelColor(Level level, Color color)
{
    // trivial example
    level.Color = color;
}
inter
  • 169
  • 1
  • 6
  • And I'm using a 2D array of my custom object (rather than a List) for specific unrelated reasons. So I cannot just use the generic foreach that comes with C# Lists. I also would like to make custom versions which only run the function on certain rows/columns or other discriminators. – inter Aug 02 '21 at 17:14
  • 5
    @Servy you closed this within 2 minutes of posting, and linked to an 11 year old question which didn't address my needs and/or I didn't understand it. – inter Aug 02 '21 at 17:22
  • In what way did the question fail to meet your needs, or what did you not understand about it? – Servy Aug 02 '21 at 17:26
  • 3
    @Servy you're right, it's the correct topic. It just feels bad to have a question I spent an hour thinking about how to ask and typing up, only for it to be closed in 2 minutes, without giving the question the chance for some helpful stranger to possibly give some advice specific to my basic level of expertise and potentially lead to a discussion. – inter Aug 02 '21 at 17:42
  • 10
    It also feels bad to see people asking a question when copy pasting their question into Google provides the answer to the exact question that they asked. Your situation isn't novel. This is a common problem, with lots of readily available information out there about how to solve it. You should be researching the information already out there on the topic and asking a question only after the existing information fails to answer your question. These things have been discussed plenty before already, we don't need to discuss them again every time someone has the same problem. – Servy Aug 02 '21 at 17:45
  • If you don't want `ForEachLevel` to have to know about the color and pass that into the method, then why are you passing that into the method? You provided the signature of the method you wanted to use earlier in the question, why did you not use that signature in your solution? – Servy Aug 02 '21 at 17:46
  • I thought this is a community site. I don't see the harm in letting an unanswered question exist.. why close it and kill any chance for getting custom help? Maybe I misunderstood the purpose of Stackoverflow – inter Aug 02 '21 at 17:52
  • 2
    See the help center for information on why certain types of questions are closed, as well as what the purpose of the site is, as you have indeed misunderstood the purpose of the site. – Servy Aug 02 '21 at 17:56
  • @Servy fair enough! Thanks for pointing me in the right direction with the delegate stuff (even if the direction was more complicated than I had hoped!) Cheers. – inter Aug 02 '21 at 18:19
  • 2
    @Servy While it's not precisely the direction the OP is asking for, couldn't the OP's issue be resolved with an iterator function that would return each element and its indices in turn -- `IEnumerable<(T, int, int)>` for a 2D array? Would that possibility -- which isn't relevant for the duplicate source -- justify reopening this question? – Zev Spitz Aug 03 '21 at 07:29
  • 2
    @ZevSpitz But it *is* precisely the question the OP asked for, so no, that *isn't* a possibility. They asked how to pass a method into another method, not how to iterate a 2D array. Knowing how to iterate a 2D array wouldn't solve their problem at all. Of course, if they *did* ask how to iterate a 2D array then *that would still be a duplicate* as that is not in fact a novel question. Why are you so interested in reopening questions to post off topic answers that don't even try to answer the question asked? – Servy Aug 03 '21 at 11:57
  • 1
    @Servy Because it's an XY problem. The OP describes their ultimate goal: _"Rather than writing the same nested `for`-loop over and over again,"_; while the OP is fixed on passing a delegate into a method which would iterate over the nested `for`-loop, that isn't the only -- or perhaps even the best -- solution for the OP's real problem. And the real problem isn't, "How do I iterate over a 2D array?" but rather "How do I avoid the boilerplate of iterating over a 2D array again and again?" – Zev Spitz Aug 03 '21 at 12:16
  • 2
    @ZevSpitz Saying that you wish the OP would have asked a different question doesn't mean they actually *did* ask a different question. You're more than welcome to tell them in comments that you think the question they asked isn't what they should have asked, and suggest that they do something different. Again, that doesn't make it appropriate to reopen a question to post an off topic answer that is also answered in many other posts instead. – Servy Aug 03 '21 at 12:28
  • go like this: https://stackoverflow.com/a/46036836/3271820 – SammuelMiranda Nov 09 '21 at 14:00

1 Answers1

0

The LINQ methods you are referring to (e.g. .Where()) and the built-in foreach keyword both rely on a generic interface in C# called IEnumerable<T>. This is what allows you to "iterate" over a collection.

You can add this iteration behavior to your own class by having it implement the interface.

public class Level { }
public class Biome : IEnumerable<Level>
{
    private List<List<Level>> _levels = new();

    public IEnumerator<Level> GetEnumerator()
        => ForEachLevelInternal().GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator()
        => GetEnumerator();

    private IEnumerable<Level> ForEachLevelInternal()
        => _levels.SelectMany(levelList => levelList);
}

If you want to then add additional LINQ-like methods to create collections over particular rows or columns, you can do so by writing a generator method.

public IEnumerable<Level> ForEachAtColumn(int index)
{
    foreach (var lst in _levels)
    {
        if (lst.Count <= 0 || index >= lst.Count) yield break;
        yield return lst[index];
    }
}

public IEnumerable<Level> ForEachAtRow(int index)
    => _levels?[index] ?? Enumerable.Empty<Level>();

If you then wanted to call a function on each element within the sequence, you can write a method for that which accepts an Action<Level> as an argument and invokes it within the loop.

public void ForEachAtColumn(int index, Action<Level> action)
{
    foreach (var level in ForEachAtColumn(index))
    {
        action(level);
    }
}

You can construct much more advanced and abstracted helper methods if you further explore the LINQ libraries (including the NuGet package System.Linq.Async).