Click here to Skip to main content
15,886,788 members
Please Sign up or sign in to vote.
3.50/5 (2 votes)
See more:
I thought I understood generics in C#, until I got something of a shock with the following simple code:
C#
List<Tuple<int,string>> list = new List<Tuple<int,string>>();
list.Add( new Tuple<int,string>( 1, "abc" ) );
list.Add( new Tuple<int,string>( 2, "def" ) );
list.Add( new Tuple<int,string>( 3, "ghi" ) );
list[ 0 ].Item1 = 4;

Compiler Error: error CS0200: Property or indexer 'System.Tuple<int,string>.Item1' cannot be assigned to -- it is read only

I have to say, I don't understand why. In this case, I am assigning an integer, so it must have 4 bytes set aside for it somewhere in the heap, so why can't I assign to it? Even if I tried to assign the string, the tuple holds a reference to a string, both of which are allocated on the heap, so again why can't I re-assign it?

Puzzled, I thought I'd work around the problem using a struct of my own as follows:
C#
private struct Foo
{
    public Foo( int i, string s )
    {
        _position = i;
        _name = s;
    }
    public int Position
    {
        get { return _position; }
        set { _position = value; }
    }
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    private int _position;
    private string _name;
}

List<Foo> list = new List<Foo>();
list.Add( new Foo( 1, "abc" ) );
list.Add( new Foo( 2, "def" ) );
list.Add( new Foo( 3, "ghi" ) );
list[ 0 ].Position = 4;

Compiler error: error CS1612: Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable

This error message I really didn't understand. I can probably think of workarounds for this, but I have to say that I'm not sure I understand what I am doing wrong.

Any help or pointers to good articles on this would be gratefully received.

Kind wishes ~ Patrick
Posted

Two issues:
First: Tuples are read-only objects, their values can be set only in the constructor.
http://msdn.microsoft.com/en-us/library/system.tuple(v=vs.100).aspx[^]

Second: Your Foo is a struct thus it has value semantics.
Value semantics[^]
If you change Foo to be a class instead of struct you'll get the behavior you're expecting.
 
Share this answer
 
v2
Comments
Ron Beyer 29-Oct-13 16:01pm    
+5'd too for covering the rest and catching the struct.
BillWoodruff 30-Oct-13 5:25am    
+5 !
This problem is not related to generics and not related to collections. It's related to a very simple thing: properties can be read-only, which happens when only a getter is defined for a property, and not a setter, as in the case of Tuple. Please start here:
http://msdn.microsoft.com/en-us/library/x9fsa0sw%28v=vs.110%29.aspx[^].

Practically, all you need is this:
C#
list[0] = new Tuple<int,string>(4, list[0].Item2);

—SA
 
Share this answer
 
Comments
Ron Beyer 29-Oct-13 16:01pm    
+5'd, the only thing I would add is that Tuples are immutable objects by design, basically that is what you are saying but figured I would add the proper term in there :)
Sergey Alexandrovich Kryukov 29-Oct-13 16:39pm    
Apparently the are. Good point. Objects are mutable or not, but separate properties can be read-only, write-only or read-write.
Thank you, Ron.
—SA
Just want to mention that you will have the same behaviour on Tuples, KeyValuePairs, .. and of course also on anonymouse types. example:
C#
List<dynamic> list = new List<dynamic>();
list.Add(new { Position = 1, Name = "abc" });
list.Add(new { Position = 2, Name = "def" });
list.Add(new { Position = 3, Name = "gah" });

list[0] = new { Position = 1, Name = "xxx" };
// not list[0].Name = "xxx"; :-(
Console.WriteLine(list[0].Name);


SA mentioned it - it has nothing todo with the generics, just the property Setters are missing in these cases... (don't hack arround with reflection to circumvent it, use a proper desing (by using a reference type)
 
Share this answer
 
v3
Thank you all for those replies.

Got to admit that, although I could see why the Tuple example was failing (I didn't realize Tuples were read-only), I still didn't see why the struct example was failing until I read the following Microsoft article:

http://msdn.microsoft.com/en-us/library/vstudio/wydkhw2c(v=vs.120).aspx[^]

I have to be honest, though, that the whole thing still feels a bit odd to me. I have a long background in C++ and I am sure this all simply worked - though I have to admit it is a few years since I have done C++ and my memory could be letting me down. Might fire up C++ later and have a play around.


Kind wishes ~ Patrick
 
Share this answer
 
Comments
johannesnestler 31-Oct-13 9:20am    
Want to think C++: You have a private field X and (per convention) a public getX and no public setX method. how to set the value from another (not friend) class? You can't, don't you agree?
Patrick Skelton 31-Oct-13 9:29am    
I agree. Maybe conventions have changed (or maybe I was just using my own), but when I used structs in C++, I didn't use get-er and set-er methods. I just allowed the fields to be public. I rarely used structs, but would use them as a neat way to manipulate tuples of data in a fairly local context. I've always worked to the rule that as soon as a struct starts to get more than a few lines of 'baggage' I make it a class.

- Patrick
johannesnestler 31-Oct-13 10:02am    
Ah I see. So again: Your missunderstanding has to do with "Properties" (the built-in .NET replacement for convention based getters and setters), they behave like variables (and simulate the syntax of fields - no paranthesis needed to call them), but in reality they are normal methods. You can decide if you want to have only a get part (or only a set part - but this doesn't make sense). So in the discussed cases this was the meaning of the compiler error (you have seen it with tuples, I showed a more "uncommon" example where (generated) code doesn't write setters for the implemented properties. So the designer of the type (Tuple) in your case just decides if something is readonly or not. If you create your own types (struct, class) you can just use public variables - no problem with that (but I hope you understand why this is considered bad practice...). But in your initial example Foo.Position is not a variable but a method (a special one though - called a property) and can't be set like this - but in this case not because you have no setter but because you created Foo as ValueType - just define it with class keyword and you will get a reference you can write on. (Update your mental differentiation between struct and class from C++ to .NET - good rule of thumb - never use a struct...). I remember when I learn this (coming from C++ too) many years ago with the naive code
System.Drawing.Rectangle rect = new System.Drawing.Rectangle();
rect.Size.Width = 10;
(which doesn't work, again cause Size is a struct (therefore a value type) and the compiler complains because it knows that it wouldn't be useful to manipulate the returned COPY)
Patrick Skelton 31-Oct-13 10:34am    
I agree completely with your rule of thumb for C# - I too have learnt that basically you just don't use struct. Every time I have tried, I have got so far into the problem and decided that I need to make it a class.

(Your 'naive' code still looks like it should work to me though. :-) )

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900