在一些C#代码中看到一些奇怪的行为,我无法解释。可能是我错过了一点重要的理解,所以希望有人可以为我打开灯。
得到一段代码如下:
IEnumberable<myObject> objects = GetObjectsFromApiCall();
for (int i = 0; i < objects.Count(); i++)
{
if (String.IsNullOrEmpty(objects.ElementAt(i).SubObject.Title))
{
SubObject sub = GetSubObjectFromDatabase((long)objects.ElementAt(i).SubObject.Id);
if (sub != null)
{
objects.ElementAt(i).SubObject.Title = sub.Title;
}
}
}
当您单步执行它时,有关此代码的所有内容似乎都能正常运行。 “对象”集合按预期填充。 “sub”被收集并具有一整套预期属性,包括填充的Title属性。执行期间不会抛出任何错误。
但是......每个Object中存在的SubObject.Title属性(只有标准的get; set;代码)固执地保持为空。
我很茫然。有人解释发生了什么事吗?
编辑:对于那些建议我不应该使用for循环和ElementAt的人,我开始使用foreach循环,但认为它可能是问题的根源,因为它每次都在获取一个新的SubObject。现在修复了,感谢您的帮助,并恢复了ForEach。
干杯, 马特
答案 0 :(得分:4)
我会这样解决:
var objects = GetObjectsFromApiCall().ToList();
然后你可以按原样保持循环(它可以工作),或者使用foreach和其他一些Linq按照其他答案的建议稍微优化它,但这并不重要:问题是你试图改变一个元素IEnumerator&lt;&gt;正如@AhmetKakıcı指出的this question所述。
答案 1 :(得分:2)
试试这个
List<myObject> objects = GetObjectsFromApiCall().ToList();
foreach(var obj in objects.Where(o => string.IsNullOrEmpty(objects.SubObject.Title)).ToList())
{
var subObject = GetSubObjectFromDatabase(obj.SubObject.Id);
if(subObject == null) continue;
obj.SubObject.Title = subObject.Title;
}
答案 2 :(得分:1)
首先,您不应将ElementAt()
用于此类代码,请使用
foreach (var o in objects)
{
if (string.IsNullOrEmpty(o.SubObject.Title))
{
o.SubObject.Title = ...;
}
}
另外,您应该注意,如果您的方法返回动态IEnumerable
,则每次调用objects.Something()
时,都会再次调用API并检索新的副本。如果是这种情况,则应使用.ToList()
方法将枚举复制到列表中。
还有一种不在列表中放置副本的方法 - 通过创建这样的动态枚举器:
objects = objects.Select(o =>
{
if (string.IsNullOrEmpty(o.SubObject.Title))
{
o.SubObject.Title = ...;
}
return o;
});
至于未正确设置的值(如果之前的事情没有帮助) - 尝试在throw new Exception(value)
属性的setter中添加Title
- 看看是否使用正确的值调用了它。
答案 3 :(得分:1)
我将函数GetObjectsFromApiCall看作如下:
public IEnumberable<myObject> GetObjectsFromApiCall(){
for(var i = 0; i < 10; i++)
{
yield return new myObject();
}
}
如果我是对的,每次调用objects.ElementAt(i)函数来获取对象时,您将通过“yield return new myObject()
”获得一个新对象。
答案 4 :(得分:1)
但是如何检查Title
属性是否已更改?你再打电话给GetObjectsFromApiCall()
吗?或者您foreach
再次通过相同的objects
实例?
IEnumerable
实例可以在每次“枚举”时创建并生成新对象。所以这是一个简单的例子。例如,定义:
class SomeObject
{
public string Title { get; set; }
}
然后我们将考虑两种类型的“source”,首先是一个数组,然后是一个像这样定义的迭代器块:
static IEnumerable<SomeObject> GetSomeSequence()
{
yield return new SomeObject { Title = "Alpha", };
yield return new SomeObject { Title = "Beta", };
yield return new SomeObject { Title = "Gamma", };
}
然后以这种方式测试:
static void Main()
{
IEnumerable<SomeObject> thingsToModify;
// set source to an array
thingsToModify = new[] { new SomeObject { Title = "Alpha", }, new SomeObject { Title = "Beta", }, new SomeObject { Title = "Gamma", }, };
foreach (var t in thingsToModify)
Console.WriteLine(t.Title);
foreach (var t in thingsToModify)
t.Title = "Changed!";
foreach (var t in thingsToModify)
Console.WriteLine(t.Title); // OK, modified
// set source to something which yields new object each time a new GetEnumerator() call is made
thingsToModify = GetSomeSequence();
foreach (var t in thingsToModify)
Console.WriteLine(t.Title);
foreach (var t in thingsToModify)
t.Title = "Changed!"; // no-one keeps these modified objects
foreach (var t in thingsToModify)
Console.WriteLine(t.Title); // new objects, titles not modified
}
结论:完全可以修改属于我们迭代的源的可变对象的状态。但是某些类型的IEnumerable
源会在每次调用时产生新的数据副本,然后对副本进行修改就没用了。