我已经解决了这个问题,但我正在试图找出它的工作原理.基本上,我使用foreach循环遍历结构列表.如果我在调用struct的方法之前包含引用当前结构的LINQ语句,则该方法无法修改结构的成员.无论是否甚至调用LINQ语句,都会发生这种情况.我能够通过将我正在寻找的值分配给变量并在LINQ中使用它来解决这个问题,但我想知道是什么导致了这一点.这是我创建的一个例子.
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 38
好吧,我已经设法用一个相当简单的测试程序重现这个,如下所示,我现在明白了.不可否认地理解它不会让我觉得任何不那么恶心,但嘿......代码后的解释.
using System;
using System.Collections.Generic;
struct MutableStruct
{
public int Value { get; set; }
public void AssignValue(int newValue)
{
Value = newValue;
}
}
class Test
{
static void Main()
{
var list = new List<MutableStruct>()
{
new MutableStruct { Value = 10 }
};
Console.WriteLine("Without loop variable capture");
foreach (MutableStruct item in list)
{
Console.WriteLine("Before: {0}", item.Value); // 10
item.AssignValue(30);
Console.WriteLine("After: {0}", item.Value); // 30
}
// Reset...
list[0] = new MutableStruct { Value = 10 };
Console.WriteLine("With loop variable capture");
foreach (MutableStruct item in list)
{
Action capture = () => Console.WriteLine(item.Value);
Console.WriteLine("Before: {0}", item.Value); // 10
item.AssignValue(30);
Console.WriteLine("After: {0}", item.Value); // Still 10!
}
}
}
Run Code Online (Sandbox Code Playgroud)
两个循环之间的区别在于,在第二个循环中,循环变量由lambda表达式捕获.第二个循环实际上变成了这样的东西:
// Nested class, would actually have an unspeakable name
class CaptureHelper
{
public MutableStruct item;
public void Execute()
{
Console.WriteLine(item.Value);
}
}
...
// Second loop in main method
foreach (MutableStruct item in list)
{
CaptureHelper helper = new CaptureHelper();
helper.item = item;
Action capture = helper.Execute;
MutableStruct tmp = helper.item;
Console.WriteLine("Before: {0}", tmp.Value);
tmp = helper.item;
tmp.AssignValue(30);
tmp = helper.item;
Console.WriteLine("After: {0}", tmp.Value);
}
Run Code Online (Sandbox Code Playgroud)
当然,每当我们复制变量时,helper我们都会获得结构的新副本.这应该是正常的 - 迭代变量是只读的,所以我们期望它不会改变.但是,您有一个方法可以更改结构的内容,从而导致意外行为.
请注意,如果您尝试更改属性,则会出现编译时错误:
Test.cs(37,13): error CS1654: Cannot modify members of 'item' because it is a
'foreach iteration variable'
Run Code Online (Sandbox Code Playgroud)
教训:
对于我来说,C#编译器是否按照此处的规范运行并不是100%清楚.我怀疑是的.即使不是,我也不想暗示团队应该付出任何努力来修复它.像这样的代码只是乞求以微妙的方式被打破.