C# 无法在 IEnumerable 中设置属性

问题描述 投票:0回答:5

在一些 C# 代码中看到一些奇怪的行为,我无法解释。可能是我缺少一些重要的理解,所以希望有人可以帮我打开灯。

得到一个如下所示的代码块:

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

当您单步执行时,该代码的所有内容似乎都可以正常工作。 “objects”集合按预期填充。 “sub”按收集方式获取,并具有一整套预期属性,包括填充的 Title 属性。执行过程中不会抛出任何错误。

但是......每个对象中存在的 SubObject.Title 属性(只有标准的 get; set; 代码)顽固地保持为空。

我很茫然。有谁解释一下这是怎么回事吗?

编辑:对于那些建议我不应该使用 for 循环和

ElementAt
的人,我从 foreach 循环开始,但认为这可能是问题的根源,因为它每次都会获取一个新的子对象。感谢您的帮助,现已修复,并且 ForEach 已恢复。

干杯, 马特

c# .net linq
5个回答
5
投票

我会这样解决它:

var objects = GetObjectsFromApiCall().ToList();

然后您可以保持循环不变(它可以工作),或者按照其他答案的建议使用 foreach 和一些 Linq 对其进行一些优化,但这并不重要:问题是您试图更改 IEnumerator 上的元素<> 正如 @Ahmet Kakıcı 所指出的这个问题 中所解释的。


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
投票

首先,这种代码不应该使用

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
- 看看是否使用正确的值调用它。


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
源每次被调用时都会生成新的数据副本,然后对副本进行修改是没有用的。


1
投票

我猜这个函数

GetObjectsFromApiCall
看起来像下面这样:

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

如果我是对的,每次调用

objects.ElementAt(i)
函数获取对象时,都会通过
yield return new myObject()
获得一个新对象。

© www.soinside.com 2019 - 2024. All rights reserved.