Это потому, что f.FullName
является ссылкой на переменную, а не на значение (как вы пытались его использовать). К тому времени, когда вы на самом деле запускаете потоки, f.FullName увеличивалось до самого конца массива.
В любом случае, зачем перебирать вещи здесь дважды?
foreach (FileInfo f in files)
{
Thread t = new Thread(delegate()
{
Console.WriteLine(f.FullName);
});
threads.Add(t);
t.Start();
}
Однако, это все еще неправильно, и, возможно, еще хуже, поскольку теперь у вас есть условие гонки, чтобы увидеть, какой поток идет быстрее: запись элемента консоли или итерация к следующему FileInfo.