【问题标题】:Modifying a property of an object in a collection修改集合中对象的属性
【发布时间】:2014-10-21 20:45:12
【问题描述】:

我有一个对象集合,并且想要修改该集合中某个对象的属性。如果我有一个对象,我的 ChangeStuff 方法可以正常工作,并且从该方法返回时对象会被修改。 (Main 中的前 4 行)

但是,在遍历集合时,我想我遗漏了一些东西,因为我更改的值似乎停留在 foreach 循环的范围内。

我不应该传递对象 by ref(或使用 out 参数),因为我没有返回新值。

对于大代码块感到抱歉,但它已尽我所能简化并仍然证明了我的问题。

    class foobar
    {
        public string string1;
        public string string2;
    }

    static void Main(string[] args)
    {
        /***** THIS WORKS!! *****/
        foobar singleFb = new foobar { string1 = "foo2", string2 = "bar2" };
        ChangeStuff(singleFb);
        Console.WriteLine(singleFb.string1 + ", " + singleFb.string2);

        Console.ReadLine(); //pause to read output

        /***** THIS DOESN'T WORK!! *****/
        List<foobar> myList = new List<foobar> { new foobar {string1 = "foo1", string2 = "bar1"}, new foobar {string1 = "foo2", string2 = "bar2"}, new foobar {string1 = "something else", string2 = "something else again"} };
        IEnumerable<foobar> fbs = myList.Where(x => x.string1.StartsWith("foo"));

        ChangeStuff(fbs);

        foreach (foobar fb in fbs)
        {
            Console.WriteLine(fb.string1 + ", " + fb.string2);
        }
        Console.ReadLine(); //pause to read output
    }

    static void ChangeStuff(IEnumerable<foobar> fbs)
    {
        foreach (foobar fb in fbs)
        {
            ChangeStuff(fb);
        }
    }

    static void ChangeStuff(foobar fb)
    {
        if (fb.string1.Contains("2"))
            fb.string1 = "changed!";
    }
}

我需要更改哪些内容才能修改集合中的对象?


编辑:另外,刚刚注意到我的收藏实际上完全丢失了“foo2”,当它回来时......很奇怪。我实际上在我的应用程序中使用 IQueryable,并且没有遇到过这个问题。 IE。我拥有所有对象,只是没有正确修改它们。不知道这里发生了什么...


编辑 2: 感谢您的回答,现在非常有意义。如果我按如下方式更改我的ChangeStuff 方法,它会按我的预期工作:
    static void ChangeStuff(foobar fb)
    {
        if (fb.string2.Contains("2"))
            fb.string2 = "changed!";
    }

【问题讨论】:

  • “foobar”是一个结构吗?请发布一个简洁但完整的代码示例,以可靠地演示该问题。
  • @PeterDuniho,对不起,我错过了复制课程。见编辑。

标签: c# linq ref


【解决方案1】:

您看到的结果是由于IEnumerable&lt;T&gt; 的延迟加载特性造成的。第二次枚举“fbs”时,属性“string1”已更改,因此它不再匹配Where 谓词。

如果您立即枚举IEnumerable 并将结果存储在具体列表中(例如,使用ToList()ToArray(),那么第二个枚举将显示您期望的结果:

IEnumerable<foobar> fbs = myList.Where(x => x.string1.StartsWith("foo")).ToList();

以下是原始代码版本中发生的情况:

List<foobar> myList = new List<foobar> { new foobar { string1 = "foo1", string2 = "bar1" }, new foobar { string1 = "foo2", string2 = "bar2" }, new foobar { string1 = "something else", string2 = "something else again" } };
IEnumerable<foobar> fbs = myList.Where(x => x.string1.StartsWith("foo"));

// here the enumeration yields objects #1 and #2
// object #2 has its "string1" property modified to "changed!"
ChangeStuff(fbs);

// here the enumeration is re-evaluated, and now object #2 no longer matches the predicate
// only object #1 ("foo1") is output
foreach (foobar s in fbs)
{
    Console.WriteLine(s.string1);
}

【讨论】:

  • 谢谢!非常有意义(参见Edit2)。我认为这与 IQueryable 的工作方式相同 - 打印属性时,它会从数据库重新加载,并且我之前的编辑会丢失?
  • 我不确定您的更改是否会被清除(也就是说,我认为数据上下文可能仍会保存您的更改,并且当您调用时它们会提交给数据库)提交更改”?)。但是是的,IQueryableIEnumerable 类似,因为它每次都会去源头,除非你首先在一个具体的列表中意识到它。
【解决方案2】:

在处理 LINQ 时要记住的最重要的事情是,这些方法不返回查询结果,它们返回代表查询本身的对象。在枚举序列时执行查询并返回项目。

fbs 是一个查询,用于获取第一个字符串以foo 开头的所有项目。它不是前两项的集合,即使当您在第一次定义查询时执行该查询时会返回这些项。

第一次执行查询并迭代结果时,您会返回两个项目并尝试同时更改它们,但实际上只有一个被ChangeStuff 更改。然后您再次执行查询以打印结果,但现在更改的项目不符合查询的条件,因此不会返回。仅返回未更改的第一项。如果您迭代列表,而不是您的查询,您将看到您更改的项目。

【讨论】:

  • 啊……这很有道理。听起来我最好的选择是在编辑集合之前转换 .ToList() ?
  • @sǝɯɐſ 这会起作用,但在这种情况下,打印结果可能应该只打印出整个列表。那将是我眼中的正确测试。无论如何,关键是您没有正确地观察到确实正在发生的变化。
  • 感谢您的精彩回答,我喜欢用斜体表示的观点,但我必须将这些观点告诉@McGarnagle,因为他的代码示例让我更清楚了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-02
  • 2017-02-23
  • 1970-01-01
  • 2013-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多