为什么 IEnumerable.Where 方法允许可以更改数据的谓词

本文关键字:数据 谓词 许可 IEnumerable Where 方法 为什么 | 更新日期: 2023-09-27 18:34:32

我正在寻找为什么允许编译以下代码行的解释:

var results = someCollection.Where(x => x.SomeBooleanProperty = true);

请注意使用单个相等运算符(也许开发人员处于SQL模式(,这是一个很容易犯的错误。这将编译并在评估结果时(例如 someCollection.ToList() ( 它在整个集合上将标志更改为 true!!

如果您使用的是实体框架或任何其他 ORM,那么这可能会被检测为更改。我刚刚在生产代码中遇到了这个问题,但幸运的是,它只在只读屏幕上引起了一个小(但完全令人困惑(的问题。试想一下,如果数据实际持久化,可能导致的可怕逻辑和数据问题。

只是为了确保我不会发疯并且它确实会更改我编写的测试

失败的数据:
[Test]
public void Test_because_im_scared()
{
    var falseProperty = new TestModel {BooleanProperty = false};
    var trueProperty = new TestModel {BooleanProperty = true};
    var list = new List<TestModel>{falseProperty, trueProperty};
    var results = list.Where(x => x.BooleanProperty = true);
    Assert.IsFalse(falseProperty.BooleanProperty);
    Assert.IsTrue(trueProperty.BooleanProperty);
    //all fine so far, now evaluate the results
    var evaluatedResults = results.ToList();
    Assert.IsFalse(falseProperty.BooleanProperty);  //test fails here!
    Assert.IsTrue(trueProperty.BooleanProperty);
}

为什么 IEnumerable.Where 方法允许可以更改数据的谓词

=运算符实际上做了两件事:

  • 将左侧的字段/属性设置为右侧的值。
  • 返回新分配的值。

这也是为什么这样的陈述有效的原因:

object item;
while ((item = getItem()) != null)   
  processItem(item);
x => x.SomeBooleanProperty = true

这个 lambda 意味着 - 对于 x,将true分配给SomeBooleanProperty 。赋值的结果也是值true

如果将其更改为:

x => x.SomeBooleanProperty

lambda 表示 - 对于 x 返回 SomeBooleanProperty 的值。

它编译是因为它是一个有效的Func<T, bool>。编译器无法判断在这种情况下,它不应该允许这样做。

看起来问题与以下事实有关

x => x.BooleanProperty = true

计算结果为 true,因此是一个有效的 where(( 谓词

我用ints尝试了一下,并且能够获得相同的行为。

[TestMethod]
public void Test_because_im_scared() {
    var falseProperty = new TestModel { BooleanProperty = false };
    var trueProperty = new TestModel { BooleanProperty = true };
    var list = new List<TestModel> { falseProperty, trueProperty };
    var results = list.Where(x => (x.IntProperty = 17) == 17) ;
    Assert.IsTrue(list.All(itm => itm.IntProperty == 0));
    //all fine so far, now evaluate the results 
    var evaluatedResults = results.ToList();
    Assert.IsTrue(list.All(itm => itm.IntProperty == 0)); // fails here, all 17
}
private class TestModel {
   public bool BooleanProperty { get; set; }
   public int IntProperty { get; set; }
}

AFAIK 这是意外的行为,IEnumerable<> 扩展应该都返回新的枚举,而不是更改原始枚举,但我在任何地方都没有看到保证。

看起来它可以用作伪 foreach((,但我不推荐它:-/

艾伦。