0

This is more or less a theoretical question, based on some hands-on experience:

In my application, I have quite some threads and my colleagues have the habit of disposing of those threads, one by one, and putting the value null in them.

I though this was a bad idea, so I created a List of those threads, and tried to do the whole thing in a loop, something like:

private List<SomeThread> _SomeThreads = new List<SomeThread>();
...
foreach (SomeThread l_SomeThread in _SomeThreads)
{
    if (l_SomeThread != null)
    {
        l_SomeThread.Dispose();
        l_SomeThread = null;
    }
}

That went sour, because of the compiler error "cannot assign to 'l_SomeThread' because it is a 'foreach iteration variable'".
"No problem", I thought, I can just use a counter for looping through the List.

For verification, I have created this simple piece of source code, and indeed, I had the same compiler error:

List<int> test = new List<int>();
test.Add(1);  test.Add(3); test.Add(5);

foreach (int i in test)
{
  i = 2*i;
}

Until here, everything is clear, as I can replace the last source by:

for (int i=0; i<test.Count(), i++)
{
  i = 2 * i;
}

But now I wonder: what about non-indexable collections, which you can only run through, using a foreach loop? Does this automatically mean that their elements cannot be altered?
Or do such collections not exist in C#, exactly for that reason? (I had a look at the C# Collections explanation and I didn't find any of them which look not "indexable")

Charlieface
  • 52,284
  • 6
  • 19
  • 43
Dominique
  • 16,450
  • 15
  • 56
  • 112
  • Note, you probably want to use "re-assigned" rather than "altered", as "altered" includes being mutated, which is a diffenent thing – canton7 Sep 21 '22 at 13:36
  • 1
    What's wrong with `test.Clear();` – Charlieface Sep 21 '22 at 13:37
  • Note the re-assignment doesn't necessarily make sense for e.g. an `IEnumerable`, where elements might be generated on-demand, so there's no element for you to replace – canton7 Sep 21 '22 at 13:38
  • @Charlieface: I would like to know how to "re-assign" another value to an element of a collection. – Dominique Sep 21 '22 at 13:38
  • @canton7: I believe that `Enumerable` is what I mean by "indexable"? In that case, are you aware of C# collections which don't implement the `IEnumerable` interface? – Dominique Sep 21 '22 at 13:40
  • `IEnumerable` is not indexable, as it does not have an indexer. You cannot access an element of an `IEnumerable` by index. – canton7 Sep 21 '22 at 13:41
  • `IEnumerable` is not indexable, only `IList` is. Each collection has its own way of dealing with things. For example, a `Queue` must read in FIFO order, and once you read then it's deleted. – Charlieface Sep 21 '22 at 13:41
  • To be honest, the actual bad idea here is using threads in the first place. And you don't need to set it to null, just `Dispose` it. Consider using tasks instead. – Charlieface Sep 21 '22 at 13:43
  • @Charlieface: I need to use threads, it's a multi-threaded application. – Dominique Sep 21 '22 at 13:44
  • 1
    It's more usual to write `async` code making use of the ThreadPool where necessary these days, rather than dealing explicitly with threads. Even where dedicated threads are needed, there are many advantages to using the `Task`-based abstractions over them – canton7 Sep 21 '22 at 13:46
  • 1
    Yeah that's what I'm saying, don't make it multi-*threaded* use `Task` and `async` – Charlieface Sep 21 '22 at 13:47
  • @MySkullCaveIsADarkPlace: I doubt that the modification of the local variable `k` will modify the items in the list `test`. – Dominique Sep 21 '22 at 14:00
  • The for-loop would have to set `test[i] = 2 * i`, not `i = 2 * i`. – Olivier Jacot-Descombes Sep 21 '22 at 16:44

2 Answers2

2

foreach uses the IEnumerable<T> interface, and do not allow reassignment of the loop variable, since that most likely is a logical bug.

What you should do it use a for-loop:

for(int i = 0; i < _SomeThreads.Count; i++){
    if (_SomeThreads[i] != null){
        _SomeThreads[i].Dispose();
        _SomeThreads[i] = null;
    }
}

Other collections like ICollection/ReadOnlyList/IEnumerable cannot have values reassigned, but you could still dispose the elements, and dispose should be idempotent. It also does not make much sense to keep a bunch of null values in the list, just remove them instead. If yo do elect to remove them you should probably iterate over the collection in the reverse order.

In my application, I have quite some threads and my colleagues have the habit of disposing of those threads, one by one, and putting the value null in them.

To me this but does not make to much sense, but it sounds super scary:

  1. Thread is not disposable
  2. You should probably be using tasks instead of threads.
  3. If 'Dispose' means Thread.Abort(), you have some serious issues
  4. Multi threaded programming is difficult. You need to be careful and know what you are doing to avoid potential bugs, and such bugs are often notoriously difficult to debug.

So I would take a step back to see what you are actually doing, and doing some reading on best practices for multi threaded programming.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • It's not a simple thread, it's a company-owned `SomeThread`, linking a regular thread to a function, which is executed during that `SomeThread`. The `Somethread` is created during the execution of a DLL, loaded by a central application and the `Dispose()` is called during the unloading of the DLL. This system is already in place for several years and I believe it's working quite well. My question is based on my attempt of putting all those `SomeThread` objects in a collection, where the disposing seems not to work, because of the mentioned compiler error and I continued reasoning further. – Dominique Sep 21 '22 at 13:58
  • 1
    @Dominique That sound like some sort of COM, at least that the most common context i know for 'Unloading' a DLL. I do not know much about COM, but I know you still need to be really careful to respect all the rules imposed by COM. In any case, there is plenty of collections without the ability to set values for an index, in fact, it is mostly Array/List that *do* allow it, most others lack an index operator, or have a get-only index operator. – JonasH Sep 21 '22 at 14:10
1

My Answer is pretty much same as @JonasH.

It is actually not problem with list rather foreach loop. If you can have a look at the foreach specification/explanation here:

  1. Forach specification explantion
  2. See this explanation: https://stackoverflow.com/a/7838178/1349365

So, to answer your question, you can change/modify any item in a List but not with foreach.

Look at this example https://dotnetfiddle.net/ap67pg, list is basically a dynamic array.

Moshi
  • 1,385
  • 2
  • 17
  • 36