如果匿名类型应该是不可变的,为什么可以更改它?

时间:2015-05-29 20:45:58

标签: 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 => a);

我不明白为什么我可以为result分配第二个值。我的意思是匿名类型是不可变的,它们在获得初始值后无法更改?

5 个答案:

答案 0 :(得分:11)

首先,没有涉及anonymous type

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

resultIEnumerable<string>,在两个LINQ语句中,您只是创建一个查询。

这是正在发生的事情:

数组创建表达式

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

查询arr并返回IEnumerable<string>

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

在arr返回IEnumerable<string>

时为结果分配新查询
result = arr.Where(a => a.EndsWith("n")).Select(a => a); 

至于理解不变性,考虑String也会看到这篇文章:Immutable types: understand their benefits and use them

答案 1 :(得分:3)

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

var anon = new { X = 5, Y = 6 };

有一些非常简单的规则:你不能表达匿名类型的类型(通常你使用var)...必须有new { ...你必须给属性的名称和值X = 5

您正在做的是使用数组初始值设定项创建string[]数组。你甚至写它:

string[] arr = ...

并且您没有修改任何内容... result是另一个变量,引用IEnumerable<>(您正在创建的新对象),然后引用另一个IEnumerable<>在结尾处你的代码有6个对象(多一点,但我们会忽略一些不可见的对象):

  • arr引用的数组(并由两个IEnumerable<>引用)
  • IEnumerable<>引用的第二个result,其中引用了arr
  • 第一个IEnumerable<>,未被任何人引用(GC将在之前或之后收集),其中引用arr
  • 3x string,全部由arr引用。请注意,IEnumerable<>是“懒惰的”,因此它们不包含对任何string的任何引用

result变量被分配两次,分配给两个不同的IEnumerable<>。重新分配变量几乎总是合法的(异常是readonly字段)。这显然是合法的:

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

答案 2 :(得分:2)

要理解的另一个有用的概念是类型,实例和变量之间的区别。

简化,类型就像一个蓝图,它描述了该类型的实例将是什么样的:

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" };

...等 请注意变量卡车和皮卡如何容纳您的实例。但变量就是这样,它们可以容纳各自类型的任何实例,所以虽然它没有多大意义,但你可以这样做:

Car tmp = truck;
truck = pickup;
pickup = tmp;

实例本身并没有改变。但变量现在有不同的实例。

上面这个示例Car类的实例是可变的。所以你可以这样做:

pickup.Doors = 99;

如果类型是不可变的,你将无法做到这一点,但你仍然可以自由地进行变量赋值,就像在示例中使用tmp变量一样,无论类型是否可变,因为赋值不会改变实例。

如上所述,您的示例不包含匿名类型,但即使它确实存在,也不会涉及您要求的任何类型的突变。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

值得扩展其他答案,以表明LINQ查询的CONCRETE解析与IEnumerable<T>不同,并且与匿名类型不变性无关。

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

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][] 

"Allan"
分别输出

,因为结果类型实际上是

System.Linq.Enumerable+WhereSelectArrayIterator`2[
    <>f__AnonymousType0`1[System.String],
    <>f__AnonymousType0`1[System.String]]

此外,如果我尝试解析IEnumerable,然后重新更新结果:

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]]

因为ToList()正在创建一个新集合。我可以在技术上随意添加和删除该集合,因为该集合本身非常愿意进行变异。

最后,这并不意味着底层的匿名类型对象不是不可变的!

result.First ().Name = "fail";

无论结果是否为包含以下错误的列表,都会失败:

  

属性或索引器&#39; AnonymousType#1.Name&#39;不能分配给它 - 它   是只读的

正好 因为它是不可变的。