1

I have a simple class:

private class ColumnAndValue
{
    private string columnName;
    private string columnValue;

    public string ColumnName
    {
        get { return columnName; }
        set { columnName = value.Trim(); }
    }
    public string ColumnValue
    {
        get { return columnValue; }
        set { columnValue = value.Trim(); }
    }
}

And a list of that class:

private List<ColumnAndValue> cvList = new List<ColumnAndValue>();

Here's where my problem comes in, when I try to load the list my values come out all the same. Here's how I'm loading it:

public void TestList()
{
    ColumnAndValue cv = new ColumnAndValue();
    cv.ColumnName = "Column 1";
    cv.ColumnValue = "Value 1";
    cvList.Add(cv);

    cv.ColumnName = "Column 2";
    cv.ColumnValue = "Value 2";
    cvList.Add(cv);

    cv.ColumnName = "Column 3";
    cv.ColumnValue = "Value 3";
    cvList.Add(cv);

    cv.ColumnName = "Column 4";
    cv.ColumnValue = "Value 4";
    cvList.Add(cv);

    List<string> c1 = new List<string>();

    c1 = cvList.Select(c => c.ColumnName).ToList();

    foreach (object obj in c1)
    {
        Console.WriteLine(obj.ToString());
    }

}

When I run this code I get four iterations of:

Column 4
Column 4
Column 4
Column 4

When I step into each cvList.Add while debugging, the first iteration shows cvList with the right information, the 2nd both values have Column 2 ... So, I tried it like this:

ColumnAndValue cv1 = new ColumnAndValue();
ColumnAndValue cv2 = new ColumnAndValue();
ColumnAndValue cv3 = new ColumnAndValue();
ColumnAndValue cv4 = new ColumnAndValue();

cv1.ColumnName = "Column 1";
cv1.ColumnValue = "Value 1";
cvList.Add(cv1);

cv2.ColumnName = "Column 2";
cv2.ColumnValue = "Value 2";
cvList.Add(cv2);

cv3.ColumnName = "Column 3";
cv3.ColumnValue = "Value 3";
cvList.Add(cv3);

cv4.ColumnName = "Column 4";
cv4.ColumnValue = "Value 4";
cvList.Add(cv4);

And everything comes out as expected:

Column 1
Column 2
Column 3
Column 4

My question is, how do I fill the list while only using one instance of cv?

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
Kerry White
  • 416
  • 2
  • 10
  • 1
    `List.Add(T item)` doesn't add a *copy* of `item` to the List, but a *reference*. The 4 list items all point back the the same original object (which you've update in the mean time) – Mathias R. Jessen Jul 24 '16 at 21:03
  • You need to re-instantiate the object in the first set of code, otherwise you are just re-adding the same item 4 times. – DavidG Jul 24 '16 at 21:03

6 Answers6

3

Change it to:

public void TestList()
{
    cvList.Add(new ColumnAndValue(){ColumnName = "Column 1", ColumnValue = "Value 1"});
    cvList.Add(new ColumnAndValue(){ColumnName = "Column 2", ColumnValue = "Value 2"});
    cvList.Add(new ColumnAndValue(){ColumnName = "Column 3", ColumnValue = "Value 3"});
    cvList.Add(new ColumnAndValue(){ColumnName = "Column 4", ColumnValue = "Value 4"});

    List<string> c1 = cvList.Select(c => c.ColumnName).ToList();

    foreach (object obj in c1)
    {
        Console.WriteLine(obj.ToString());
    }

}

Because List.Add() add a reference to your ColumnAndValue instance and not a copy, thus, if you change the name of the object, it will be changed in all the places you use it.

You can read more about it at When is a C# value/object copied and when is its reference copied? and Value Types and Reference Types on MSDN

Community
  • 1
  • 1
Thomas Ayoub
  • 29,063
  • 15
  • 95
  • 142
  • Perfect @ThomasAyoub! I just tried it and it works great. Question: I had no idea how to search for that. Could you tell me what that's called? – Kerry White Jul 24 '16 at 21:07
  • Ah! I get it, initially, I was passing by reference so in the end, the reference wasn't changing only the value and I was adding it four times. Great response! Thanks again. – Kerry White Jul 24 '16 at 21:15
2

you need to new your cv every time

public void TestList()
    {
        ColumnAndValue cv = new ColumnAndValue();
        cv.ColumnName = "Column 1";
        cv.ColumnValue = "Value 1";
        cvList.Add(cv);

cv = new ColumnAndValue();
        cv.ColumnName = "Column 2";
        cv.ColumnValue = "Value 2";
        cvList.Add(cv);

cv = new ColumnAndValue();
        cv.ColumnName = "Column 3";
        cv.ColumnValue = "Value 3";
        cvList.Add(cv);

cv = new ColumnAndValue();
        cv.ColumnName = "Column 4";
        cv.ColumnValue = "Value 4";
        cvList.Add(cv);

        List<string> c1 = new List<string>();

        c1 = cvList.Select(c => c.ColumnName).ToList();

        foreach (object obj in c1)
        {
            Console.WriteLine(obj.ToString());
        }

    }

cv is a handle to your object. if you don't new it every time, you are just changing the value of the same object. your cvlist just have references to the same object and you update that every time

Ashkan S
  • 10,464
  • 6
  • 51
  • 80
2

What is happening is that your List is actually a list of references to the same object you already created. You keep changing the data in the same object but you keep adding references to it in your list.

For more info, check the difference between reference types and value types.

Edit: There was an answer to this effect but it was deleted for some reason. the behavior you're expecting is that of Value types, which are defined with a struct keyword. Value types are copied by value. So in your code, changing the class to a struct would do the behavior you expect. Keep in mind it is still weird looking code and you should get used to instanciating new objects with new data because your code would be more readable that way. The ability to change the data in your existing objects, aka Mutability, is a powerful tool, but it is a very easy to abuse and go wrong using it, as you just found out.

asibahi
  • 857
  • 8
  • 14
1

You're supposed to use new instances. If you add an item to a list, then change the item properties, you are just editing the same item. Adding it to the list again will only add another reference to the item.

Here's a short-hand way of doing it:

private class ColumnAndValue
{
    private string columnName;
    private string columnValue;

    public string ColumnName
    {
        get { return columnName; }
        set { columnName = value.Trim(); }
    }
    public string ColumnValue
    {
        get { return columnValue; }
        set { columnValue = value.Trim(); }
    }
    public ColumnAndValue(string colName, string colValue)
    {
       columnName = colName;
       columnValue = colValue;
    }
}

Then create your list using:

private List<ColumnAndValue> cvList = new List<ColumnAndValue>();
cvList.Add(new ColumnAndValue("Column 1", "Value 1"));
cvList.Add(new ColumnAndValue("Column 2", "Value 2"));
cvList.Add(new ColumnAndValue("Column 3", "Value 3"));
cvList.Add(new ColumnAndValue("Column 4", "Value 4"));
Andy-Delosdos
  • 3,560
  • 1
  • 12
  • 25
1

You see, ListT.Add method does not create a copy of its reference parameter. It just copies and stores the reference to your class (same) instance four times.

Here's the code for ListT.Add referencesource.microsoft.com

Your question is contradictory, if you want only one instance, you'll get only one instance. ListT.Add does not copy (does not create instances of reference types).

1

When you add the cv object to the list, the list maintains a reference to that object.

This means that you are adding the same object to your list over and over again, and changing the same object.

What you want to do is add different variables to your list:

public void TestList()
{
    ColumnAndValue cv = new ColumnAndValue();
    cv.ColumnName = "Column 1";
    cv.ColumnValue = "Value 1";
    cvList.Add(cv);

    // Now cv is a reference to a different object
    cv = new ColumnAndValue();
    cv.ColumnName = "Column 2";
    cv.ColumnValue = "Value 2";
    cvList.Add(cv);

    // Now cv is a reference to a different object
    cv = new ColumnAndValue();
    cv.ColumnName = "Column 3";
    cv.ColumnValue = "Value 3";
    cvList.Add(cv);

    // Now cv is a reference to a different object
    cv = new ColumnAndValue();
    cv.ColumnName = "Column 4";
    cv.ColumnValue = "Value 4";
    cvList.Add(cv);

    List<string> c1 = new List<string>();

    c1 = cvList.Select(c => c.ColumnName).ToList();

    foreach (object obj in c1)
    {
        Console.WriteLine(obj.ToString());
    }

}
André Sampaio
  • 309
  • 1
  • 5