Why can an anonymous type be changed if it should be immutable?
I thought I had a good understanding of anonymous type, but this little piece of code confused me a bit:
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);
I don't understand why the second value is assigned to me result
. I mean, the idea of anonymous types is not immutable, that they cannot be changed after they have their original value?
source to share
First, there is no anonymous type .
This one string[] arr = { "Agnes", "Allan", "Benny" };
is an array creation expression .
result
- IEnumerable<string>
, and in both LINQ operators, you are just creating a query.
This is what happens:
array creation expression
string[] arr = { "Agnes", "Allan", "Benny" };
arr request and returns IEnumerable<string>
var result = arr.Where(a => a.StartsWith("A")).Select(a => a);
assigns the results to a new arr query, returning IEnumerable<string>
result = arr.Where(a => a.EndsWith("n")).Select(a => a);
As for understanding immutability, consider String
also see this article: Immutable Types: Understand Their Benefits and Use Them
source to share
You have an anonymous type when you do something like:
var anon = new { X = 5, Y = 6 };
There are some simple rules: you cannot express the type of an anonymous type (as you often do var
) ... there should be new {
... you must provide a name for the property and a value X = 5
.
What you are doing is creating an array string[]
using an array initializer. You even write this:
string[] arr = ...
And you don't change anything ... result
is another variable referencing IEnumerable<>
(the new object you are creating) and then referencing another IEnumerable<>
. At the end of your code, you have 6 objects (a bit more, but we'll ignore some invisible objects):
- Array referenced by
arr
(and referenced by twoIEnumerable<>
) - Second
IEnumerable<>
referencedresult
which has a link toarr
- The first one
IEnumerable<>
, which is not referenced by anyone (GC collects it before or later), which has a link toarr
- 3x
string
, all refer toarr
. Note that theyIEnumerable<>
are "lazy", so they do not contain links to anystring
The variable result
is assigned twice, up to two different ones IEnumerable<>
. Almost always the right to reassign variables (except for fields readonly
). this is clearly legal:
string foo = "Foo";
foo = "Bar";
source to share
Another useful concept to understand is the distinction between type, instance, and variable.
Simplification, the type is like a blueprint, it describes what an instance of the type would look like:
class Car
{
public int Doors {get; set;}
public string EngineType { get; set;}
}
The above code describes the type. You can make many instances of this type:
Car truck = new Car { Doors = 2, EngineType = "BigEckingEngine" };
Car pickup = new Car { Doors = 5, Engine Type = "4 cylinder" };
etc ... Notice how the variable trucks and pickup will fit your instances. But variables are just like that, they can place any instance of their type, so while it doesn't really matter, you can do this:
Car tmp = truck; truck = pickup; pickup = tmp;
The instances themselves have not changed. But now the variables have different instances.
Instances of this example Car
class above are mutable. So, you can do this:
pickup.Doors = 99;
If the type is immutable, you cannot do this, but you can still assign variables, as in the variable example tmp
, no matter what type is changed or not, since such assignment does not change instances.
As noted, your example does not contain an anonymous type, but even if it did, it does not include any mutation you are asking for.
source to share
LINQ methods like Where()
and Select()
do not modify the underlying array. It creates a new object. The created one result
is of type IEnumerable<string>
, LINQ just filters the array, so if you iterate over it later, you just get the values that match Where
and Select
, but your object arr
remains unchanged.
source to share
It is worth expanding on the other answers to show that CONCRETE LINQ query resolution is not the same as IEnumerable<T>
and has nothing to do with immutability of anonymous type.
If you created the following array of anonymous types:
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();
in my case
<>f__AnonymousType0`1[System.String][]
and
"Allan"
are inferred accordingly, since the type of the result is actually
System.Linq.Enumerable+WhereSelectArrayIterator`2[
<>f__AnonymousType0`1[System.String],
<>f__AnonymousType0`1[System.String]]
Also, if I try to resolve IEnumerable and then re-update the result:
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();
I get output again
"Allan"
However, in this case, my result type was overrated to
System.Collections.Generic.List`1[<>f__AnonymousType0`1[System.String]]
since it ToList()
creates a new collection. I can technically add and remove this collection at will, since the collection itself is quite ready for mutation.
Finally, this does not mean that the main object of the anonymous type is not immutable!
result.First ().Name = "fail";
The error, regardless of the result, will be a list with the following error:
No assignment to property or index 'AnonymousType # 1.Name' - it is read-only
precisely because it is immutable.
source to share