I encountered a behaviour I can´t quite understand when I was trying to populate a GridView. Consider the following code:
DataTable table = GetStockMovement((int)ViewState[MOVEMENT_ID], false);
MovedProducts = from DataRow row in table.Rows
select new Product()
{
ProductId = (int)row["ProductId"],
ProductNo = row["ProductNo"].ToString(),
Name = row["ProductName"].ToString(),
Quantity = OriginalProducts.ToList().Find(p => p.ProductId == (int)row["ProductId"]).Quantity,
QuantityDiff = (int)row["Change"]
};
foreach (Product product in MovedProducts)
{
Console.WriteLine(product.ProductId + ": " + product.Quantity);
}
Console.WriteLine();
foreach (Product product in OriginalProducts)
{
product.Quantity -= 1000;
}
foreach (Product product in MovedProducts)
{
Console.WriteLine(product.ProductId + ": " + product.Quantity);
}
The output of this was like this:
1: 10
2: 4
1: -990
2: -996
Product.Quantity is an int
MovedProducts and OriginalProducts are IEnumerables that are stored in ViewState.
Why does a product in MovedProducts change when I change in OriginalProducts? Are they not two totally different objects stored in different places in memory?
If I instead of the LINQ-expression used:
List<FPRSProduct> completedProducts = new List<FPRSProduct>();
foreach (DataRow row in table.Rows)
{
Product tmp = new Product();
tmp.ProductId = (int)row["ProductId"];
tmp.ProductNo = row["ProductNo"].ToString();
tmp.Name = row["ProductName"].ToString();
tmp.Quantity = OriginalProducts.ToList().Find(p => p.ProductId == (int)row["ProductId"]).Quantity;
tmp.QuantityDiff = (int)row["Change"];
completedProducts.Add(tmp);
}
Everything worked out as I expected it to, that is I get the same numbers before and after changing the Quantity of the OriginalProducts.
LINQ utilizes what we call deferred execution. When you write out a linq query, you are not actually performing the tasks that query defines…you are simply defining it. The behavior defined by your query is executed at the moment of use…in your case, in the two foreach statements. The query is executed TWICE for EACH foreach, so even though data is copied…its copied from the source twice at two distinct points in time. The second foreach around your MovedProducts is retrieving the updated data in your OriginalProducts because execution of the query is deferred until the second foreach is actually executed.
Note this KEY line of code in the ‘select new’ clause of your link query:
Since you update OriginalProducts between the first and second foreach, even though you are creating a distinc, new list of Product objects…since processing of the query is deferred, you execute the above line for each product twice. Once for the first foreach, and once again for the second foreach.