2013-03-08 76 views
6

在一些C#代码中看到了一些奇怪的行为,我不知所措。可能是我错过了一个重要的理解,所以希望有人可以为我开灯。C#未能在IEnumerable中设置属性

得到的代码块看起来像这样:

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属性。在执行过程中不会引发错误。

...但是... SubObject.Title属性(它只是有标准的get;设定;代码)存在于每个对象固执地保持为空。

我不知所措。任何人都可以解释发生了什么?

编辑:对于那些建议我不应该使用for循环和ElementAt,我开始使用foreach循环,但认为它可能是问题的来源,因为它每次都获取新的SubObject。现在修复,感谢您的帮助,并恢复了ForEach。

干杯, 马特

+4

你去了哪里:[更新IEnumerable中的item属性,但属性不保留设置?](http://stackoverflow.com/a/9104212/93732) – 2013-03-08 12:58:24

+0

此代码有可能会非常慢甚至不好笑。 – ChaosPandion 2013-03-08 12:58:46

+0

你可以复制/粘贴你的* actual *代码,而不是* *看起来像* actual *代码的东西吗? – ken2k 2013-03-08 13:00:00

回答

4

我会解决这个问题是这样的:

var objects = GetObjectsFromApiCall().ToList(); 

然后,你可以保持环路是(它的工作原理),或优化有点用的foreach和一些LINQ的其他答案的建议,但它确实并不重要:问题在于您试图更改IEnumerator上的元素,如@AhmetKakıcı指出的this question中所述。

+1

-1这实际上是错误的。修改IEnumerable返回的元素没有问题。实际上,当你在'ToList()'后面使用'foreach'时,你正在使用'IEnumerable',因为'List '实现'IEnumerable '。一个问题可能是数据库查询的*延迟执行*,但这绝对不是因为IEnumerable接口的简单存在...... – ken2k 2013-03-08 15:13:17

+0

你说得对,我读得太快......问题不在于IEnumerable但它的实施方式。这就是为什么使用ToList()有意义。感谢您的澄清。 – Larry 2013-03-08 16:01:47

+0

@ ken2k正确。查看我的答案,了解如何修改“IEnumerable”返回的项目。 – 2013-03-08 16:13:39

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)以获取Title属性 - 查看是否正在使用正确的值调用该属性。

+0

“首先,您不应该使用ElementAt()代替这种类型的代码。”为什么? – 2013-03-08 13:43:27

+0

.NET会每次使用'Enumerator.MoveNext()'枚举'i'次来获取值。它比'list [i]'方法慢。 – 2013-03-08 13:55:19

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; 
} 
1

我来宾功能GetObjectsFromApiCall看起来像以下:

public IEnumberable<myObject> GetObjectsFromApiCall(){ 
    for(var i = 0; i < 10; i++) 
    { 
     yield return new myObject(); 
    } 
} 

如果我是正确的,每次打电话objects.ElementAt(I)函数来获取对象,您将通过“yield return new myObject()”获得一个新对象。

+0

这是一个很好的理论。我正在考虑发布一个这样的例子。 – 2013-03-08 13:37:18

+0

哦,你应该改变“objects.ElementAt(i).SubObject.Title = sub.Title;”到“var obj = objects.ElementAt(i).SubObject; obj.Title = sub.Title;” – fengyj 2013-03-08 13:40:12

+0

关于你的评论:它会改变什么? – 2013-03-08 15:08:52

1

但是,如何检查Title属性是否发生了变化?你再拨打GetObjectsFromApiCall()吗?还是你foreach再次通过相同的objects实例?

IEnumerable实例可能会在每次“枚举”时创建并产生新对象。所以这里有一个简单的插图示例。对于这个示例,定义:

class SomeObject 
{ 
    public string Title { get; set; } 
} 

然后,我们将考虑两种类型的“源极”,第一阵列,然后这样定义的迭代器块:

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源在每次调用时都会产生新的数据副本,然后修改副本无用。