Why does adding a new value to list<> overwrite previous values in the list<>
I'm essentially trying to add multiple items to a list but at the end all items have the same value equal to last item.
public class Tag
{
public string TagName { get; set; }
}
List<Tag> tags = new List<Tag>();
Tag _tag = new Tag();
string[] tagList = new[]{"Foo", "Bar"};
foreach (string t in tagList)
{
_tag.tagName = t; // set all properties
//Add class to collection, this is where all previously added rows are overwritten
tags.Add(_tag);
}
The code above produces list of two items with TagName
set to "Bar" when I expect one for "Foo"
and one with "Bar"
. Why all items have the same properties in the resulting list?
Bonus point for explanation why changing public class Tag
to public struct Tag
makes this code work as expected (different items have different values).
If that matter my actual goal is to create derived collection class, but since issue happens with just list it likely optional, still showing what my goal is below.
Following a few tutorials and such I was able to successfully create a collection class which inherits the functionality needed to create a DataTable which can be passed to a Sql Server's stored procedure as a table value parameter. Everything seems to be working well; I can get all of the rows added and it looks beautiful. However, upon closer inspection I notice that when I add a new row, the data for all of the previous rows is overwritten with the value for the new row. So if I have a row with a string value of "foo" and I add a second row with the value "bar", the second row will be inserted (making a DataTable with two rows) but both rows will have the value "bar". Can anyone see why this would be? Here is some of the code, which works but has been a bit simplified (the Tag class has been reduced for ease of explanation).
The following is the Collection class's:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using Microsoft.SqlServer.Server;
namespace TagTableBuilder
{
public class TagCollection : List<Tag>, IEnumerable<SqlDataRecord>
{
IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
{
var sdr = new SqlDataRecord(
new SqlMetaData("Tag", SqlDbType.NVarChar)
);
foreach (Tag t in this)
{
sdr.SetSqlString(0, t.tagName);
yield return sdr;
}
}
}
public class Tag
{
public string tagName { get; set; }
}
}
These are called as follows:
//Create instance of collection
TagCollection tags = new TagCollection();
//Create instance of object
Tag _tag = new Tag();
foreach (string t in tagList)
{
//Add value to class propety
_tag.tagName = t;
//Add class to collection, this is where all previously added rows are overwritten
tags.Add(_tag);
}
You're using the same instance of the Tag
object inside the loop, so each update to the TagName
is to the same reference. Move the declaration inside the loop to get a fresh object on each pass of the loop:
foreach (string t in tagList)
{
Tag _tag = new Tag(); // create new instance for every iteration
_tag.tagName = t;
tags.Add(_tag);
}
For bonus part - when you change Tag
from class
to struct
copy operation (that happens when you call tags.Add(_tag)
) copies whole instance (essentially creating new one) unlike in original class
case when only reference to the same single instance is copied into the parameter of the call and then to the list's element (see C# pass by value vs. pass by reference for explanation on how struct
passed to method calls).
In the loop where you add the tags to the collection, you're using the same object instance of Tag. Essentially, you're setting a Tag's name to the first value in tagList and adding it to the collection, then you're changing that same Tag's name to the second value in tagList and adding it again to the collection.
Your collection of Tags contains several references to the same Tag object! Instantiate _tag inside the for loop each time before setting the Tag's name and adding it to the collection.