C#无法在IEnumerable中设置属性

时间:2013-03-08 12:55:59

标签: c# .net linq

在一些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。

干杯, 马特

5 个答案:

答案 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源会在每次调用时产生新的数据副本,然后对副本进行修改就没用了。