我认为我对匿名类型有很好的理解,但是这个小代码片段让我有些困惑:
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
分配第二个值。我的意思是匿名类型是不可变的,它们在获得初始值后无法更改?
答案 0 :(得分:11)
首先,没有涉及anonymous type。
此string[] arr = { "Agnes", "Allan", "Benny" };
是array creation expression。
result
是IEnumerable<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
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只会对数组进行过滤,因此如果您稍后对其进行迭代,您将获得与Where
和Select
匹配的值,但是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;不能分配给它 - 它 是只读的
正好 因为它是不可变的。