为什么不能修改元组类型列表元素的值?

c#

在 C# 8.0 中,我可以通过访问字段名称直接修改元组内的值:

(string name, int score) student = ("Tom", 100);
student.name = "Jack";
Console.WriteLine(student);

我可以修改列表元素的属性如下:

var list = new List<Student>();  // assume I have a Student class which has a Name property
list.Add(new Student { Name = "Tom" });
list[0].Name = "Jack";
Console.WriteLine(list[0]);

但是为什么我不能像这样修改元组类型元素的值呢?

var list = new List<(string name, int score)>();
list.Add(("Tom", 100));
list[0].name = "Jack"; // Error!
Console.WriteLine(list[0]);

回答

元组 ( ValueTuple) 是一个结构体。您实际上会收到元组的副本,而不是像您的示例那样返回对该值的引用Student

对该副本的更改不会反映在列表中,并且会被丢弃。编译器足够聪明,可以识别这一点并阻止您这样做。

如果它确实编译了,它将类似于以下内容:

var list = new List<(string name, int score)>(); list.Add(("Tom", 100));
var copy = list[0];
copy.name = "Jack"; 
Console.WriteLine(copy.name);    // Jack
Console.WriteLine(list[0].name); // still Tom

如果您没有正确使用可变结构,它们可能会很危险。编译器只是在做它的工作。

您可以使用以下方法解决此问题:

var list = new List<(string name, int score)>(); list.Add(("Tom", 100));
var copy = list[0];
copy.name = "Jack"; 
list[0] = copy;                  // put it back
Console.WriteLine(copy.name);    // Jack
Console.WriteLine(list[0].name); // Jack

在线试用

如果您使用数组(string, int)[]而不是 a List<(string, int)>,由于数组元素访问的工作方式,这不是问题:

var arr = new (string name, int score) [] { ( "Tom", 10 ) };
arr[0].name = "Jack";
Console.WriteLine(arr[0].name); // Jack

在线试用

此行为不是List您的元组类型所独有的。您将在元素为值类型的任何集合中遇到此问题(当然,除非它们提供ref元素访问器)。

请注意,当具有通过方法调用发生变异readonly可变值类型的字段时,也存在类似的问题。这可能会更加阴险,因为没有发出错误或警告:

struct MutableStruct { 
   public int Val; 
   public void Mutate(int newVal) {
     Val = newVal;
   } 
} 
class Test {
    private readonly MutableStruct msReadonly;
    private MutableStruct msNormal;
    public Test() {
      msNormal = msReadonly = new MutableStruct(){ Val=5 };
    } 
    public void MutateReadonly() {
         Console.WriteLine(msReadonly.Val); // 5
         msReadonly.Mutate(66);             // defensive copy! 
         Console.WriteLine(msReadonly.Val); // still 5!!! 
    } 
    public void MutateNormal() {
         Console.WriteLine(msNormal.Val);   // 5
         msNormal.Mutate(66);
         Console.WriteLine(msNormal.Val);   // 66
    } 
}
new Test().MutateReadonly();
new Test().MutateNormal();

在线试用

ValueTuple是对框架和语言的一个很好的补充。但是有一个原因,您经常会听到[Mutable] structs are evil.在大多数情况下您不应该达到这些限制。如果您发现自己经常陷入这种模式,我建议转向 a record,它是一种引用类型(因此不会遇到这些问题)并且可以简化为类似元组的语法。


回答

可变值类型是邪恶的,很难理解为什么这会打印“Tom”而不是“Jack”:

(string name, int score) student = ("Tom", 100);
(string name, int score) student2 = student;
student.name = "Jack";
Console.WriteLine(student2);

原因是你总是创建一个副本。因为这并不明显,您应该避免使用可变值类型。为了避免人们陷入那个陷阱,编译器只允许直接通过属性修改对象(如上)。但是,如果您尝试通过方法调用来执行此操作,则会收到编译器错误“无法修改...的返回值,因为它不是变量”。

所以这是不允许的:

list[0].name = "Jack";

它将创建 的新副本ValueTuple,分配一个值,但不会在任何地方使用或存储它。

这会编译,因为您将它分配给一个新变量并通过属性修改它:

(string name, int score) x = list[0];
x.name = "Jack"; // Compiles 

所以它编译但再次给你一个令人惊讶的结果:

Console.WriteLine(list[0]);  // Tom

在此处阅读更多相关信息:不要定义可变值类型


以上是为什么不能修改元组类型列表元素的值?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>