I’ve got a work around for this issue, but I’m trying to figure out why it works . Basically, I’m looping through a list of structs using foreach. If I include a LINQ statement that references the current struct before I call a method of the struct, the method is unable to modify the members of the struct. This happens regardless of whether the LINQ statement is even called. I was able to work around this by assigning the value I was looking for to a variable and using that in the LINQ, but I would like to know what is causing this. Here’s an example I created.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WeirdnessExample
{
public struct RawData
{
private int id;
public int ID
{
get{ return id;}
set { id = value; }
}
public void AssignID(int newID)
{
id = newID;
}
}
public class ProcessedData
{
public int ID { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<ProcessedData> processedRecords = new List<ProcessedData>();
processedRecords.Add(new ProcessedData()
{
ID = 1
});
List<RawData> rawRecords = new List<RawData>();
rawRecords.Add(new RawData()
{
ID = 2
});
int i = 0;
foreach (RawData rawRec in rawRecords)
{
int id = rawRec.ID;
if (i < 0 || i > 20)
{
List<ProcessedData> matchingRecs = processedRecords.FindAll(mr => mr.ID == rawRec.ID);
}
Console.Write(String.Format("With LINQ: ID Before Assignment = {0}, ", rawRec.ID)); //2
rawRec.AssignID(id + 8);
Console.WriteLine(String.Format("ID After Assignment = {0}", rawRec.ID)); //2
i++;
}
rawRecords = new List<RawData>();
rawRecords.Add(new RawData()
{
ID = 2
});
i = 0;
foreach (RawData rawRec in rawRecords)
{
int id = rawRec.ID;
if (i < 0)
{
List<ProcessedData> matchingRecs = processedRecords.FindAll(mr => mr.ID == id);
}
Console.Write(String.Format("With LINQ: ID Before Assignment = {0}, ", rawRec.ID)); //2
rawRec.AssignID(id + 8);
Console.WriteLine(String.Format("ID After Assignment = {0}", rawRec.ID)); //10
i++;
}
Console.ReadLine();
}
}
}
Okay, I’ve managed to reproduce this with a rather simpler test program, as shown below, and I now understand it. Admittedly understanding it doesn’t make me feel any less nauseous, but hey… Explanation after code.
The difference between the two loops is that in the second one, the loop variable is captured by a lambda expression. The second loop is effectively turned into something like this:
Now of course each time we copy the variable out of
helperwe get a fresh copy of the struct. This should normally be fine – the iteration variable is read-only, so we’d expect it not to change. However, you have a method which changes the contents of the struct, causing the unexpected behaviour.Note that if you tried to change the property, you’d get a compile-time error:
Lessons:
It’s not 100% clear to me whether the C# compiler is behaving as per the spec here. I suspect it is. Even if it’s not, I wouldn’t want to suggest the team should put any effort into fixing it. Code like this is just begging to be broken in subtle ways.