C# 如果假定匿名类型是不可变的,为什么可以更改它?

C# 如果假定匿名类型是不可变的,为什么可以更改它?,c#,linq,C#,Linq,我认为我对匿名类型有很好的理解,但是这个小代码片段让我有点困惑: string[] arr = { "Agnes", "Allan", "Benny" }; var result = arr.Where(a => a.StartsWith("A")).Select(a => a); // Why can I do the below, if arr is immutable? result = arr.Where(a => a.EndsWith("n")).Select(a

我认为我对匿名类型有很好的理解,但是这个小代码片段让我有点困惑:

string[] arr = { "Agnes", "Allan", "Benny" };

var result = arr.Where(a => a.StartsWith("A")).Select(a => a);

// Why can I do the below, if arr is immutable?
result = arr.Where(a => a.EndsWith("n")).Select(a => a);

我不明白的是为什么允许我为
结果
分配第二个值。我的意思是,匿名类型不是不可变的,即在获得初始值后不能更改吗?

首先,不涉及任何内容。


这个
string[]arr={“Agnes”,“Allan”,“Benny”}是一个

result
IEnumerable
,在这两个LINQ语句中,您只是创建了一个查询

这就是正在发生的事情:

数组创建表达式

查询arr并返回
IEnumerable

在arr返回时为结果分配一个新查询
IEnumerable


至于理解不变性,请考虑
String
另请参阅本文:

当您执行以下操作时,您有一个匿名类型:

var anon = new { X = 5, Y = 6 };
这里有一些非常简单的规则:不能表示匿名类型的类型(因此经常使用
var
)。。。必须有一个
新的{
…您必须为属性指定一个名称和一个值
X=5

您要做的是使用数组初始值设定项创建一个
string[]
数组。您甚至要编写它:

string[] arr = ...
您没有修改任何内容…
result
是另一个变量,引用一个
IEnumerable
(您正在创建的新对象),然后引用另一个
IEnumerable
,在代码末尾,您有6个对象(稍多,但我们将忽略一些不可见的对象):

  • arr
    引用的数组(并由两个
    IEnumerable
    引用)
  • 第二个
    IEnumerable
    ,由
    result
    引用,它引用了
    arr
  • 第一个
    IEnumerable
    ,没有被任何人引用(GC将在之前或之后收集),它引用了
    arr
  • 3x
    string
    ,都由
    arr
    引用。请注意,
    IEnumerable
    是“惰性的”,因此它们不包含对任何
    字符串的任何引用
结果
变量被分配两次,分配给两个不同的
IEnumerable
。重新分配变量几乎总是合法的(例外是
只读
字段)。显然,这样做是合法的:

string foo = "Foo";
foo = "Bar";

Where()
Select()这样的LINQ方法
不要更改基础数组。它会创建一个新对象。创建的
结果属于
IEnumerable
类型,LINQ只是过滤数组,因此如果您稍后对其进行迭代,您将只获得与
Where
Select
匹配的值,但您的
arr
对象将保持不变。

她需要理解的有用概念是类型、实例和变量之间的区别

简化,类型就像一个蓝图,它描述了类型实例的外观:

class Car
{
   public int Doors {get; set;}
   public string EngineType { get; set;}
}
上面的代码描述了类型。您可以创建此类型的多个实例:

Car truck = new Car { Doors = 2, EngineType = "BigEckingEngine" };  
Car pickup = new Car { Doors = 5, Engine Type = "4 cylinder" };
等等。。。 请注意变量truck和pick是如何存放实例的。但变量只是这样,它们可以存放各自类型的任何实例,因此,尽管这没有多大意义,但您可以这样做:

Car tmp = truck;
truck = pickup;
pickup = tmp;
pickup.Doors = 99;
实例本身没有改变,但变量现在拥有不同的实例

上述示例
Car
类的实例是可变的。因此您可以执行以下操作:

Car tmp = truck;
truck = pickup;
pickup = tmp;
pickup.Doors = 99;
如果类型是不可变的,您将无法做到这一点,但您仍然可以自由地使用
tmp
变量执行变量赋值,而不管类型是否可变,因为这样的赋值不会更改实例


如前所述,您的示例不包含匿名类型,但即使它包含匿名类型,也不涉及您正在询问的任何类型的变异。

值得扩展其他答案,以表明LINQ查询的具体解决方案与
IEnumerable
不同,也与匿名类型immut无关能力

如果创建了以下匿名类型数组:

var arr = new[] { new { Name = "Agnes"}, new { Name = "Allan" }, new { Name = "Benny" }};

arr.GetType().Dump();

var result = arr.Where(a => a.Name.StartsWith("A")).Select(a => a)
result = arr.Where(a => a.Name.EndsWith("n")).Select(a => a);

result.Dump();
就我而言

<>f__AnonymousType0`1[System.String][] 
分别输出,因为结果类型实际上是

System.Linq.Enumerable+WhereSelectArrayIterator`2[
    <>f__AnonymousType0`1[System.String],
    <>f__AnonymousType0`1[System.String]]
我再一次得到了

"Allan"
但是,在本例中,我的结果类型已重新评估为

System.Collections.Generic.List`1[<>f__AnonymousType0`1[System.String]]
将失败,无论结果是包含以下错误的列表:

无法将属性或索引器“AnonymousType#1.Name”分配给--它 是只读的


准确地说,因为它是不可变的。

@Habib:谢谢!为了理解这一点,如果我的LINQ语句应该返回匿名类型,那么它应该是什么样子呢?@brinch匿名类型很难从方法返回,也很难传递给其他方法。它们用于在内部存储“临时数据”一个方法。返回一个匿名类型是一个可怕的黑客攻击,或者你可以使用
动态
(但通常你不应该返回一个匿名对象)@Habib:为了记录,我想这会使res匿名:var res=from n in names,其中n.StartsWith(“n”)选择new{n};@brinch,是的,它将返回您的
IEnumerable
,其中
T
将是匿名类型
var result = arr.Where(a => a.Name.StartsWith("A")).Select(a => a).ToList();
result = arr.Where(a => a.Name.EndsWith("n")).Select(a => a).ToList();
"Allan"
System.Collections.Generic.List`1[<>f__AnonymousType0`1[System.String]]
result.First ().Name = "fail";