Foreach 循环将迭代变量传递给带有和不带 ref 的方法 - 不明白为什么它不允许它带有 ref



今天早上我一直在努力理解以下内容,我希望有人能很好地解释为什么这行不通。我在复制我的问题LinqPad上做了一个例子:

void Main()
{
var myPeople = new People();
myPeople.MyPeople.Add(new Person { Name = "Joe", Surname = "Doe" });
foreach (var person in myPeople.MyPeople)
{
//this is intermediate variable is still a reference to the iteration variable
var intermediate = person;
myPeople.ChangeName(ref intermediate);
//both names were changed, so why cant i just pass in 'person' directly since its still changing it.
intermediate.Name.Dump();
person.Name.Dump();
}
}
public class People
{
public People()
{
MyPeople = new List<Person>();
}
public List<Person> MyPeople { get; set; }
public void ChangeName(ref Person person)
{
person.Name = "Changed";
}
}
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}

所以这篇文章解释了foreach变量是一个只读变量,这就是为什么你不能用另一个替换它的原因 - 是有道理的,但是如果我用ref将其传递到上面的方法ChangeName,编译器将不允许它 CS1657。但是,如果我删除ChangeName方法上的ref,它将允许我传入foreach变量,但从技术上讲,它仍然是一个reference,没有明确说这是一个ref,对吗?

那么为什么会这样呢?此外,如果我将其分配给上面的中间变量,那么它将允许我将其作为ref传入,即使它仍然是对原始变量的引用,因此从技术上讲指向相同的内存位置,这意味着相同的对象,对吗?

所以我的问题实际上是为什么 C# 编译器不允许我将迭代变量传递给带有ref

的方法进一步澄清拉法隆的问题: 为什么编译器不允许 myPeople.ChangeName(ref person),但允许 myPeople.ChangeName(ref 中间),当person变量指向与intermediate变量相同的对象时?

最终,CS1657 上的注释解释了所有这些; 常规foreach的 l 值被认为是"只读的" - 你不能这样做:

person = new Person();

例如。ref用法要求参数不是只读的,否则我们违反了只读的承诺,因为该方法可能会修改本应为只读的内容。在这种情况下,由于Person是一个类,因此这仅与实际引用本身有关,而不与基础对象有关。

不过,有个好消息:我们现在有in,就像ref,但对于只读场景,所以: 将public void ChangeName(ref Person person)更改为public void ChangeName(in Person person),它应该可以工作:

myPeople.ChangeName(in person);

对此的限制是,在ChangeName方法中,您不能执行以下操作:

public void ChangeName(in Person person)
{
person = new Person(); // invalid
}

强调:这种in用法并不是真正用于类;它是为struct,特别是readonly struct- 以避免复杂和大型值类型的防御性堆栈副本。

在最新版本的 C# 中,还有一个ref readonly概念,其中 l 值是一个引用Foo(对于任何类型的Foo)。这需要一个带有public ref Foo Current {get;}访问器而不是public Foo Current {get;}访问器的自定义迭代器,即它被显式绑定到 ref-return 中。当 l 值是引用时,您可以在采用ref的方法中使用它。


重要的强调,尽管我意识到OP知道这一点:在这种情况下你不需要ref,因为Person本身就是一个类;代码在没有ref的情况下可以正常工作,并且对象将正确更新。

传递 ref 中间体时,传递对变量中间体的引用,而不是对它所指向的对象。如果将新的 Person 分配给 ChangeName 中的 person 变量,中间值将更改。

这就是为什么不允许在foreach中传递ref person的原因,因为您可以更改foreach变量的值。

最新更新