Home > Enterprise >  Using variables inside for loop scope outside of it. Possible in python, but not in C#
Using variables inside for loop scope outside of it. Possible in python, but not in C#

Time:07-02

In python, I've gotten into the habit of using variables inside a for loop outside of its "scope". For example:

l = ["one", "two", "three"]

for item in l:
    if item == "one":
        j = item

print(j)

You can't quite do this in C#. Here are the several attempts I made:

First attempt

I declare a variable j of type string, assign the selected item to it inside the foreach loop scope and then refer back to it once I exit the foreach loop scope:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> l = new List<string> { "one", "two", "three" };
        string j;       

        foreach (string item in l)
        {
            if (item == "one")
            {
                j = item;
            }
        }

        Console.WriteLine(j);
    }
}

The compiler throws an error:

Microsoft (R) Visual C# Compiler version 4.2.0-4.22252.24 (47cdc16a) Copyright (C) Microsoft Corporation. All rights reserved.

test.cs(19,27): error CS0165: Use of unassigned local variable 'j'

Second attempt

Moving the declaration inside the foreach is also no good, because the variable is not recognized outside of the scope at all:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> l = new List<string> { "one", "two", "three" };

        foreach (string item in l)
        {
            string j;

            if (item == "one")
            {
                j = item;
            }
        }

        Console.WriteLine(j);
    }
}

The compiler throws the following error:

Microsoft (R) Visual C# Compiler version 4.2.0-4.22252.24 (47cdc16a) Copyright (C) Microsoft Corporation. All rights reserved.

test.cs(20,27): error CS0103: The name 'j' does not exist in the current context

Third attempt:

Moving the declaration in the innermost scope and assigning the value to the variable results in a similar problem as the second attempt:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> l = new List<string> { "one", "two", "three" };

        foreach (string item in l)
        {

            if (item == "one")
            {
                string j = item;
            }
        }

        Console.WriteLine(j);
    }
}

The compiler complains because at line 19 the variable j is not recognized.

Microsoft (R) Visual C# Compiler version 4.2.0-4.22252.24 (47cdc16a) Copyright (C) Microsoft Corporation. All rights reserved.

test.cs(19,27): error CS0103: The name 'j' does not exist in the current context

The solution

One possible solution is as follows:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> l = new List<string> { "one", "two", "three" };
        string j = "test";

        foreach (string item in l)
        {

            if (item == "one")
            {
                j = item;
            }
        }

        Console.WriteLine(j);
    }
}

But I find this to be quite ugly and lacking robustness, because I have to assign some dummy value to j. For instance, perhaps the string "test" is recognized by other parts of my program and would make it behave in unexpected ways.

Question

Is there an elegant alternative to achieve this kind of behavior in C#, or am I missing something?

CodePudding user response:

First attempt is more correct, but the compiler is telling you that in certain cases (where your collection is empty), j will never be assigned to. Your solution is nearly there, but instead of j="test", I would use j = null, and then after your foreach, make sure j is not null before using it

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> l = new List<string> { "one", "two", "three" };
        string j = null;

        foreach (string item in l)
        {

            if (item == "one")
            {
                j = item;
            }
        }

        if(j!=null) <-- check j has been assigned to, before using it.
        {
            Console.WriteLine(j);
        }
        else
        {
            Console.WriteLine("Item was not found");
        }
    }
}

CodePudding user response:

Don't write the loop at all. What is your expectation for how often an element in your list should occur1? From your sample, it appears it should be at least once, but maybe exactly once.

Then pick the appropriate LINQ method that expresses your expectations clearly:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        List<string> l = new List<string> { "one", "two", "three" };
        string j = l.Single(item=>item=="one");       

        Console.WriteLine(j);
    }
}

The above will throw an exception if exactly one element doesn't match our criteria. You may instead pick First or Last (or with OrDefault) variants to express your requirements and expectations.


1 Or to put it another way, what is your expectations if the list is empty or no elements in the list match your search criteria?

Your python code appears to continue just assuming that neither of those are true. C# was designed to help you spot where shaky assumptions such as this are coming into play and prevent you from writing code with such assumptions.

Unassigned variables have, historically, been a massive source of hard to track down bugs because they either contain garbage (often but not always provoking an error quite soon) or a default value (which may look like a genuine value for quite some time), with the symptoms arising in a completely different location to the broken assumption.

CodePudding user response:

Maybe a little more sofisticated way to fix this is to initialize the variable like this:

string j = string.Empty;

CodePudding user response:

You are not really using each of the values in the loop but just get the LAST one that matches. As an alternative you could use Linq and then query your list and supply a default value for example:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        List<string> myList = new List<string>{"one", "two", "three"};
        string defaultValue = "whateverwant";
        string matchMe = "one";
        string j = myList.Where(item => item == matchMe)
            .DefaultIfEmpty(defaultValue)
            .Select(item => item).FirstOrDefault();
        Console.WriteLine(j);
    }
}

Using your first example you can set or check for a null for a default

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> l = new List<string> { "one", "two", "three" };
        string j = null;// or a default value string so we avoid the ternary

        foreach (string item in l)
        {
            if (item == "one")
            {
                j = item;
                break; // we found one so no need to keep looping
            }
        }

        j = string.IsNullOrEmpty(j)?"default":j
        Console.WriteLine(j);
    }
}
  • Related