Повторное объявление локальной переменной в новом потоке
Может быть это выглядит логично с точки зрения объявления переменной в новом потоке. Но даже в лямбда-функциях невозможно объявить одноимённую переменную, если она была объявлена выше.
Можно ли это считать недочётом самого языка программирования или я в чём-то ошибаюсь?
static void Main(string[] args)
{
var bag = new ConcurrentBag<int>();
var threadCollection = new List<Thread>();
for (int i_index = 0; i_index < 5; ++i_index)
{
new Thread(() =>
{
Thread.CurrentThread.Name = i_index.ToString();
Console.WriteLine(Thread.CurrentThread.Name);
for (int i_index = 0; i_index < 100; ++i_index)
{
Thread.Sleep(75);
bag.Add(i_index);
}
Console.WriteLine(Thread.CurrentThread.Name + "ended");
}).Start();
}
Console.Read();
foreach (var item in bag)
{
Console.Write($"{item} ");
}
Console.WriteLine(bag.Count);
}
Ответы (2 шт):
Если строго по спецификации: то в рамках анонимной функции i - это outer variable, а не local variable, так что разрешено объявлять local variable с тем же именем:
7.15.5 Outer variables
Any local variable, value parameter, or parameter array whose scope includes the lambda-expression or anonymous-method-expression is called an outer variable of the anonymous function.
Потоки тут особо ни при чем, просто внезапно с точки зрения анонимной функции локальная переменная того метода, в котором она объявлена, не является локальной переменной в этой самой анонимной функции. Это же касается и лямбд при компиляции их в анонимные функции:
static void Some()
{
int i = 1;
Action a = () => { // у вас коде этот Action отдается в конструктор Thread
int i = 2; // нет ошибки,
};
}
Компилятор разворачивает такую конструкцию во что-то типа:
class __Locals1
{
public int x;
public void __Method1() {
int x = 2;
}
}
static void Some()
{
__Locals1 __locals1 = new __Locals1();
__locals1.x = 1;
Action a = new Action(__locals1.__Method1);
}
А объявлять переменные с тем же именем, что поля класса - можно. Но фишка интересная, до вашего вопроса я с ней не сталкивался :)
EDIT: По поводу обращения к i до ее объявления в теле цикла - да, в рамках scope локальной переменной нельзя обращаться к ней до объявления, for в примере просто ограничивает scope:
static void Some()
{
int i = 1;
Action a = () => { // вместо Thread, потоки ни при чем
i = 2; // обращение к outer variable
{ int i = 2; } // вместо for, scope для local variable i - от скобки до скобки
};
}
без скобок упадет:
static void Some()
{
int i = 1;
Action a = () => { // вместо Thread, потоки ни при чем
i = 2; // обращение к local variable i в ее scope до объявления
int i = 2; // scope от скобки до скобки
};
}
Можно объявлять, недочётом не является.
Могу объяснить тем, что у потока свой стек, анализатор это учитывает и разрешает имена.
Сам часто так делаю.
Внутренний int i_index у него локальный, а внешний int i_index он получает по ссылке, например в момент i_index.ToString();
Но, ни в коем случае не пишите так, как именно вы это сделали, потому-что если у вас будет небольшая задержка в выполнении, например:
Thread.Sleep(200);
Thread.CurrentThread.Name = i_index.ToString();
То вывод изменится:
Ожидание Реальность
1 5
2 5
3 5
4 5
5 5
4ended 5ended
3ended 5ended
5ended 5ended
2ended 5ended
1ended 5ended
Чтобы всё работало корректно, нужно копировать значение итератора для потока:
for (int i_index = 0; i_index < 5; ++i_index)
{
int i_index_copy = i_index; //желательно делать в первую очередь
new Thread(() =>
{
Thread.Sleep(200);
Thread.CurrentThread.Name = i_index_copy.ToString();
Console.WriteLine(Thread.CurrentThread.Name);
for (int i_index = 0; i_index < 100; ++i_index)
{
Thread.Sleep(75);
bag.Add(i_index);
}
Console.WriteLine(Thread.CurrentThread.Name + "ended");
}).Start();
}
Но, т.к. вы делали ++i_index вам нужно учесть смещение, а вывод будет следующим:
Ожидание Реальность Новый код
1 5 1
2 5 0
3 5 2
4 5 3
5 5 4
4ended 5ended 4ended
3ended 5ended 0ended
5ended 5ended 1ended
2ended 5ended 3ended
1ended 5ended 2ended