今天早上我一直在努力理解以下内容,我希望有人能很好地解释为什么这行不通。我在复制我的问题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变量的值。