Are structs 'pass-by-value'?
Asked Answered
A

9

49

I've recently tried to create a property for a Vector2 field, just to realize that it doesn't work as intended.

public Vector2 Position { get; set; }

this prevents me from changing the values of its members (X & Y)

Looking up information on this, I read that creating a property to a Vector2 struct returns only a copy of the original object and not a reference.

As a Java developer this confuses me.

When are objects in C# passed by value and when are they passed by reference?
Are all struct objects passed by value?

Aerostation answered 12/2, 2012 at 18:50 Comment(6)
You need to show more. This should work as you expect it to.Roxi
a struct is a value type - so it is passed by value - for each reference type on the other hand (i.e. class) by default a copy of the reference is passed (the reference itself is passed by value)Complicate
@b1naryj the stack vs heap part is an implementation detail that is almost always an unhelpful way of thinking about it.Rationalism
@b1naryj Nobody misses that because it doesn’t exist: class Foo { Vector2 x; } – now the struct value x is allocated on the heap, not on the stack.Hillery
Structs are passed by value. As you have discovered, making a mutable struct is a "worst practice" because doing so is so confusing. Make structs immutable values, just like integers are immutable values.Leavis
@EricLippert Structs are passed by value in the same way references are passed by value, it's the default for parameter passing in C#, not because it's a value/reference type. There's a difference between passing by value/reference & value/reference types (which is the point I feel you are glazing over too casually). This is not the case when using ref/out, as you are passing by reference at this point as Konrad mentions (& Jon skeet has many times, hence his blog post on the matter).Declan
H
106

It is important to realise that everything in C# is passed by value, unless you specify ref or out in the signature.

What makes value types (and hence structs) different from reference types is that a value type is accessed directly, while a reference type is accessed via its reference. If you pass a reference type into a method, its reference, not the value itself, is passed by value.

To illustrate, imagine we have a class PointClass and a struct PointStruct, defined analogously (omitting irrelevant details):

struct PointStruct { public int x, y; }

class PointClass { public int x, y; }

And we have a method SomeMethod that takes these two types by value:

static void ExampleMethod(PointClass apc, PointStruct aps) { … }

If we now create two objects and call the method:

var pc = new PointClass(1, 1);
var ps = new PointStruct(1, 1);

ExampleMethod(pc, ps);

… we can visualise this with the following diagram:

diagram

Since pc is a reference, it doesn’t contain the value in itself; rather, it references an (unnamed) value somewhere else in memory. This is visualised by the dashed border and the arrow.

But: for both pc and ps, the actual variable is copied when calling the method.

What happens if ExampleMethod reassigns the argument variables internally? Let’s check:

static void ExampleMethod(PointClass apc, PointStruct aps); {
    apc = new PointClass(2, 2);
    aps = new PointStruct(2, 2);
}

Output of pc and ps after calling the method:

pc: {x: 1, y: 1}
ps: {x: 1, y: 1}

→ ExampleMethod changed a copy of the values, and the original values are unaffected.

This, fundamentally, is what “pass by value” means.

There’s still a difference between reference and value types, and that comes into play when modifying members of the value, not the variable itself. This is the part that trips people up when they are confronted with the fact that reference types are passed by value. Consider a different ExampleMethod.

static void ExampleMethod(PointClass apc, PointStruct aps) {
    apc.x = 2;
    aps.x = 2;
}

Now we observe the following result after calling the method:

pc: {x: 2, y: 1}
ps: {x: 1, y: 1}

→ The reference object was changed, whereas the value object wasn’t. The diagram above shows why that is: for the reference object, even though pc was copied, the actual value that both pc and apc reference remains identical, and we can modify that via apc. As for ps, we copied the actual value itself into aps; the original value cannot be touched by ExampleMethod.

Hillery answered 12/2, 2012 at 18:55 Comment(28)
While this is (technically) correct, I feel that it's a confusing answer. C# (and .NET in general) chose the Value Type vs. Reference Type abstraction. Deconstructing that abstraction might be technically true, but in practice it's not productive.Tattler
@Avner Sorry but you are incorrect. The distinction is crucial because otherwise you end up with a flawed understanding and faulty code. If you though that references were passed by reference (and many programmers do!) you will expect that modifications to the reference in the method are visible to the caller (and again, many programmers do expect that). So my answer isn’t only technically true, it’s of practical relevance (and thus productive). Paraphrasing Feynman: if reality is too confusing for you, that’s just too bad, choose a different reality.Hillery
@Konrad: I tend to agree with Avner. Yes, as you delve deeper into understanding the language, it can become useful to know that you are always passing values to methods. But, in general, the value is not considered to be the reference itself. For example, if I have string s = "abc";, most people would consider "abc" to be the value. And, in this case, it can be helpful to think of the argument as being passed by reference.Wilder
If a parameter of a reference type is passed by reference, then assigning a new object to the parameter variable will alter the original variable of the caller. If, however, it is passed by value, this will not affect the original value. It is very important to makes this distinction. Reference types and value types can both be passed by reference or by value.Brigittebriley
@Konrad: It seems like a semantic difference to me. Ask anyone what the value of a string is and they'll say it's the text the variable references. So saying the string is passed by reference can also be correct. Indeed, it is the terminology Microsoft uses. Perhaps I missed it, but I'm having a hard time thinking of examples where your distinction is helpful to regular developers.Wilder
@Jonathan Just as an aside, Microsoft sucks at terminology. Ignore them, they are huge trolls. As for an example where the distinction matters, there are enough questions on SO where this tripped somebody up. It should be easy to see that the distinction is helpful.Hillery
@JonathanWood: The value of a string object is of course the text, but the value of a string variable is the reference to the string object. The term passing by reference is when you use the ref keyword, and it's only confusiong if you use it when you actually mean passing a reference.Inge
@JonathanWood to add to Guffa's comment: If M(myString); is passing by reference, how do you explain M(ref myString); or M(out myString);? It seems much more useful to say that a reference type is an object that may only be accessed by way of a reference. Confusion arises when we shortcut that by saying, e.g., that string.Substring returns a new string. It doesn't. It returns a reference to a new string. When simplified terminology leads to confusion, it's better to stop over-simplifying rather than to extend the simplification into more confusing territory like param modifiers.Rationalism
Obligatory reference to Jon Skeet blog post on the topicDeclan
–1 for making saying everything is pass by reference in your first statement in bold, then burying the heart of the matter that for classes it is the const reference that is passed at the end of your answer. Order matters, if you invert your answer people get what they actually need.Houston
@ChrisMarisic I can't help that, you're simply wrong. C# is pass by value. Deal with it. That is the heart of the matter.Hillery
@KonradRudolph being pedantically correct doesn't mean it's actually useful. And great retaliatory downvote ;)Houston
@ChrisMarisic I don’t retaliate with downvotes. And you misunderstand what this answer entails. It is emphatically not about pedantism, as I’ve explained in a previous comment (with, currently, 22 upvotes), it’s about actually understanding C#’s object model. Saying anything other than that C# passes by value (a) spreads a fundamental misunderstanding of the object model, and (b) leads people to have false expectations about their code’s semantics, which makes it buggy.Hillery
@ChrisMarisic You’re saying that being able to reason correctly about semantics and having an accurate understanding of the type system is pedantic? Houston, we have a problem.Hillery
The pedantics are choosing to use words that are purely ambiguous in their context. If there was no ambiguity none of these threads would exist. My answer removes all ambiguity whereas your answer drastically increases the ambiguity due to its paragraph structure. I mean how more ambiguous can you make a sentence that starts with EVERYTHING* (asterisk terms and conditions apply) then further reframe the emboldened proclamation.Houston
@Chris Let us continue this discussion in chat.Hillery
This answer is definitely confusing. It infers that reference type objects need to be passed with a ref or out param when that's just not the case. Non-reference types do need this. Simple as that. Even if the reference is being passed by value, it doesn't matter, you are just passing around a 'reference value" at that point, and one could argue either way that it's still being passed by reference. This answer is so grey I've gone color blind. Instead of answering the question directly you have now confused everyone here.Bajaj
@Bajaj No, you're simply completely wrong, end of discussion. Read the comments: This has been chewed up again and again ad nauseam.Hillery
I need come to Konrad's defense here since his answer is as clear and correct as the detractors are misguided. Like just about anything, you can't use value types properly without understanding their semantics, and unlike @JonathanWood, I mean that word correctly (see also here) here--i.e., not that the differences are 'unimportant' but rather that they are precisely meaningful and knowable...Skidway
...Even cars, often mis-cited as something you don't need to understand to use, require specific technical knowledge to operate, and we call safe drivers 'responsible' rather than denouncing them as 'pedantic'. If one considers the well-established behavior of .NET value types too be too complex, it's a simple choice to not use them. IMHO that's the only tenable argument here.Skidway
The 1st statement is wrong. Value types are passed by value and reference types are passed by reference.Coprophilous
@Coprophilous No. Sorry. You’re unambiguously wrong and, what’s more, this misunderstanding has been repeatedly brought up as you can read in the comments — there’s no need to bring it up yet again! Anyway, this isn’t up for debate. C# (and most similar languages) are pass-by-value, as this answer explains in great detail.Hillery
After coming back to this post time and time again, I have to give it this to @KonradRudolph. My apologies on my previous comment above. I've gained what I would hope to consider quite a bit of beneficial knowledge int he last few years lol, and can finally comprehend clearly what you are saying. This is actually a well formed explanation, I doubt many others would be able to explain it in a more comprehensible way. Nice work. Sucks that you got so much flak for this. Apologies once more for my ignorance. God speed good sir.Bajaj
The pratical effects of the difference are very important to be known, no matter the words or semantical words used. Another important thing to note is that you cannot change internal values of structs inside lists (instead you must create a new-changed instance if the item and replace it). Classes,instead, can be changed in loco.Tilley
@KonradRudolph You've defeated your own argument - you said "references were passed by reference" when you meant "references were passed by reference by value", which according to you is a crucial distinction.Loudmouthed
@Loudmouthed No. I didn’t say that. I said that if somebody thinks that, then they’re mistaken.Hillery
@KonradRudolph I know, I'm saying your description of what people think is ambiguous according to your own definition of "by reference".Loudmouthed
@Loudmouthed I am merely repeating/paraphrasing a common misconception, in order to address it. I really don’t understand your issue with this.Hillery
I
25

A struct is a value type, so it's always passed as a value.

A value can either be a reference type (object) or a value type (struct). What's passed around is always a value; for a reference type you pass the value of the reference to it, for a value type you pass the value itself.

The term by reference is used when you use the ref or out keywords to pass a parameter. Then you are passing a reference to the variable that contains the value instead of passing the value. Normally a parameter is always passed by value.

Inge answered 12/2, 2012 at 18:53 Comment(3)
So, there is no easy way to implement properties with a struct?Aerostation
@Acidic: Yes. If you need to set the properties individually of a struct property, then it should most likely not be a struct anyway.Inge
@Acidic: it is easy, but from a java perspective, not what you want. In the .NET world, structs are supposed to be immutable, so you should not allow for the the change of individual properties. The language supports changing individual properties, but since everything is passed by value, you always change the property on a copy of the struct, not the original (unless you use the ref or out keyword, but that would create really awkward code for changing the properties mutable structs).Carolinacaroline
W
6

.NET data types are divided into value and reference types. Value types include int, byte, and structs. Reference types include string and classes.

structs are appropriate instead of classes when they just contain one or two value types (although even there you can have unintended side effects).

So structs are indeed passed by value and what you are seeing is expected.

Wilder answered 12/2, 2012 at 18:56 Comment(2)
Sorry Strings are immutable are NOT passed by reference in terms of a reference generally understood. If it would be as you said, a string could be changed by some callee if passed as parameter! A class's fields, if passed as a parameter, can be changed, so it must be a reference and not a plain copy it.Thoroughgoing
@Martin: Strings are immutable as a class detail but they behave like any other object being passed as an object, and the string itself is not placed on the stack but a reference to its object.Laughingstock
H
5

Foreward: C# while managed still has the core memory idioms created by C. Memory can be reasonably viewed as a giant array where the index in the array is labeled the "memory address". A pointer is a numeric index of this array aka a memory address. The values in this array can either by data or a pointer to another memory address. A const pointer is a value stored in this array at some index which cannot change. A memory address inherently exists and can never change however the value that is located at that address can always change if it is not const.

Pass by class

A class / reference type (which includes string) is passed by a const pointer reference. Mutation will affect all usages of this instance. You cannot change the address of the object. If you attempt to change the address with either assignment or new you will in effect create a local variable that shares the same name as the parameter in the current scope.

Pass by copy

Primitives / ValueTypes / structs (strings are neither even though they disingenuously pose as them) are completely copied when returned from a method, property, or received as a parameter. Mutation of a struct will never be shared. If a struct contains a class member, what is copied is the pointer reference. This member object would be mutable.

Primitives are never mutable. You cannot mutate 1 to 2, you can mutate the memory address that currently refers to 1 to the memory address of 2 in the current scope.

Pass by true reference

Requires the usage of out or ref keywords.

ref will allow you to alter the pointer a new object or assign an existing object. ref will also allow you to pass a primitive / ValueType / struct by it's memory pointer to avoid copying the object. It would also allow you to replace the pointer to a different primitive if you assign to it.

out is semantically identical to ref with one minor difference. ref parameters are required to be initialized where out parameters are allowed to uninitialized as they are required to be initialized in the method that accepts the parameter. This is commonly shown in the TryParse methods and eliminates the need for you to have int x = 0; int.TryParse("5", out x) when the initial value of x would serve no purpose.

Houston answered 25/3, 2015 at 15:37 Comment(2)
Managed references are semantically different from pointers or anything else a storage location can hold. If a valid pointer holds bit pattern 0x12345678, then as long as the pointer remains valid bit pattern 0x12345678 will continue to identify the same object. By contrast, the GC may change the bit patterns necessary to identify managed objects at any time, provided that it modifies the bit patterns stored in every reachable reference to make it keep identifying the same object.View
Adding to what @View notes, in fact you can even have (managed) references to (unsafe) native pointers (but not the reverse, obviously), and this includes by using the new C#7 ref local feature: int i; int *pi = &i; ref int* rpi = ref pi;Skidway
M
3

Just to illustrate the different effects of passing struct vs class through methods:

(note: tested in LINQPad 4)

Example

/// via https://mcmap.net/q/349587/-are-structs-39-pass-by-value-39
void Main() {

    // just confirming with delegates
    Action<StructTransport> delegateTryUpdateValueType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    Action<ClassTransport> delegateTryUpdateRefType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    // initial state
    var structObject = new StructTransport { i = 1, s = "one" };
    var classObject = new ClassTransport { i = 2, s = "two" };

    structObject.Dump("Value Type - initial");
    classObject.Dump("Reference Type - initial");

    // make some changes!
    delegateTryUpdateValueType(structObject);
    delegateTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after delegate");
    classObject.Dump("Reference Type - after delegate");

    methodTryUpdateValueType(structObject);
    methodTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after method");
    classObject.Dump("Reference Type - after method");

    methodTryUpdateValueTypePassByRef(ref structObject);
    methodTryUpdateRefTypePassByRef(ref classObject);

    structObject.Dump("Value Type - after method passed-by-ref");
    classObject.Dump("Reference Type - after method passed-by-ref");
}

// the constructs
public struct StructTransport {
    public int i { get; set; }
    public string s { get; set; }
}
public class ClassTransport {
    public int i { get; set; }
    public string s { get; set; }
}

// the methods
public void methodTryUpdateValueType(StructTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateRefType(ClassTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateValueTypePassByRef(ref StructTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

public void methodTryUpdateRefTypePassByRef(ref ClassTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

Results

(from LINQPad Dump)

Value Type - initial 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - initial 
ClassTransport 
UserQuery+ClassTransport 
i 2 
s two 

//------------------------

Value Type - after delegate 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after delegate 
ClassTransport 
UserQuery+ClassTransport 
i 12 
s two, appended delegate 

//------------------------

Value Type - after method 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after method 
ClassTransport 
UserQuery+ClassTransport 
i 112 
s two, appended delegate, appended method 

//------------------------

Value Type - after method passed-by-ref 
StructTransport 
UserQuery+StructTransport 
i 1001 
s one, appended method by ref 


Reference Type - after method passed-by-ref 
ClassTransport 
UserQuery+ClassTransport 
i 1112 
s two, appended delegate, appended method, appended method by ref 
Marthena answered 13/2, 2013 at 14:58 Comment(0)
B
2

The problem is, that the getter returns a copy of Vector2. If you change the coordinates like this

obj.Position.X = x;
obj.Position.Y = y;

You only change the coordinates of this ephemeral copy.

Do this instead

obj.Position = new Vector2(x, y);

This has nothing to do with by value or by reference. Value2 is a value type and get returns this value. If the vector had a reference type (class), get would return this reference. return returns values by value. If we have a reference type, then these references are the values and are returned.

Brigittebriley answered 12/2, 2012 at 18:54 Comment(46)
But this forces me to create a new object everytime I want to to change the value of the field. Eww.Aerostation
It is not an object, so no new object is created; however, the constructor initializes the vector.Brigittebriley
Of course it is an object. Why would you suppose that an instance of a struct is not an object?Leavis
I think of objects as reference types, as instances of classes.Brigittebriley
@Acidic: If you are changing the value of the field then change the value of the field! Vectors are logically immutable, just like integers. You don't think "I'm going to change the number 12 so that it is now 13". You think "I'm going to replace the integer stored in this variable, 12, with a new integer, 13." Don't think "I'm going to mutate (1,2) to (1,3)". Think "I'm going to replace the vector in this variable, (1,2) with a different vector (1,3)." Think about values as values.Leavis
If it pleases you to believe the falsehood that structs are not objects, you go ahead and have that pleasant belief. I recommend against teaching that falsehood to others though; it seems likely to confuse them.Leavis
Wikipedia says Object (computer science), "In computer science, an object is any entity that can be manipulated by the commands of a programming language, such as a value, variable, function, or data structure. (With the later introduction of object-oriented programming the same word, 'object', refers to a particular instance of a class)".Brigittebriley
@Eric Glad to hear you say that. However, it has been pointed out to me before that the MSDN (at least in some places) makes the same distinction as Olivier here, and refers as “object” specifically to reference types.Hillery
@Eric: Storage locations of value types hold values, not objects. Every structure type has a corresponding class type (which inherits from ValueType) into which it is implicitly convertible, but storage locations of value type can't hold those. A variable of type Point holds 32 bits for an X value and 32 bits for a Y value. No sign of any managed object, or Object, or any other such thing anywhere. A Point may be implicitly cast to ValueType or Object, but a point which has been so cast is a different type from a Point which could sit in a storage location.View
@Olivier: That wikipedia definition is terrible. In C#, instances of structs are objects.Leavis
@Konrad: Thanks for the link; I'll follow up with the documentation manager.Leavis
@supercat: storage locations of value types hold values. Values of value type are objects. This idea that you have that there is a "corresponding class type" is very Java-ish, and is a pleasant fiction, but it is a fiction. If you and Olivier enjoy believing this pleasant fiction, that's fine with me, but I'd find it unfortunate that you'd want to teach it to others. The implementation details of how a value is laid out in memory have nothing whatsoever to do with whether a thing is an "object" or not.Leavis
@EricLippert: If a conversion of one thing to some particular type (e.g. IEnumerator<string>) is reference-preserving and conversion of another things to that same type is not reference preserving, I would regard the two things as being of semantically-different types. The system may in one case use the name List.Enumerator<String> to refer to the type of an object instance on the heap, and in the other case use the same name to refer to the type of the storage location holding the thing, but the former type of thing will exhibit class semantics and the latter will exhibit value semantics.View
@EricLippert: I would suggest that part of the definition of inheritance is that if type X inherits from Y, casts from Y to X or vice versa will be representation-preserving; if a cast from X to Y or from Y to X is anything but representation-preserving, that implies that neither X nor Y inherits from the other. Boxed structures clearly inherit from ValueType, and they meet that requirement (they also exhibit reference semantics). The things stored in storage locations of struct types, however, do not meet that requirement.View
@View if you define inheritance to mean something different from its definition on the c# type system, then you will arrive at a conclusion that is different than the one dictated by the definition of the c# type system, and relatively meaningless in the context of that system.Rationalism
@supercat: That is absolutely not the definition of "inheritance" in C#. And so phoog is correct -- if you start with a completely different definition, then you're going to arrive at completely different conclusions. You are free to use any definition you like, but again, when you teach it to other people, you're just impeding their ability to communicate with people who have already agreed to use the definition in the specification.Leavis
I think that we have to make a distinction between objects in the .NET type system and objects as a general concept in object-oriented programming. In .NET, all types derive directly or indirectly from System.Object and are therefore objects. However, you can find many sources that conceptually define objects as instances of classes. Just one example, American National Standard Dictionary of Information Technology (ANSDIT) - The letter “O”: "In object-oriented programming, an object is an instance corresponding to a class definition."Brigittebriley
the confusion here likely rest on the overload of the word class which has meaning in the type system but also is the means of defining a reference type, as opposed to as value type. @Eric's definition for c# is of course going to be correct he 'works on it for a living'. It is unfortunate that there are some 'leaks' in the abstraction between the "world of c# according to the type system" and the world of c# at other points where there is some messy behaviors. For me a classic case is the System.Enum type, which is indeed a type, but constraints refuse to play nice with itForrester
@OlivierJacot-Descombes "class" has a domain-specific meaning in C# where it is contrasted with "struct"; we should be careful not to extend that to the word "class" in general discussions of OOP. This is relevant to a question I've been thinking of asking: in c# is there a word that encompasses both classes and structs (i.e., types whose fields we define and where we can write methods and properties)? For example, C# might have been defined with declarations as class val instead of struct and class ref instead of class without misusing the general OOP concept of "class".Rationalism
@OlivierJacot-Descombes Perhaps this is unwelcome muddying of the waters, but I feel compelled to point out that not all types derive from System.Object -- interface types, for example. All values are of a type that derives from object (outside unsafe code, since pointers do not derive from object). See Eric's blog: blogs.msdn.com/b/ericlippert/archive/2009/08/06/…Rationalism
@ShuggyCoUk: The type system uses the same Type object to describe storage locations (fields, etc.) of e.g. type List.Enumerator<String>, and boxed heap instances of List.Enumerator<String>, but they behave differently. The differences are most apparent with mutable structs (probably why Mr. Lippert hates them--they present a major leak in the "unified type system" abstraction) but can be demonstrated even with immutable structs (e.g. KeyValuePair<Object, String>).View
@EricLippert: Given a boxed instance of List<String>.Enumerator, would there be any way without using Reflection of determining whether it was a class type or value type? As far as I can tell, in all scenarios not involving Reflection, a boxed instance of that type will in every way behave exactly as it would if it were a class. If one were to use Reflection to get the type of that instance and create an array of that type, the array elements would behave as value types, but I can't think of any non-Reflection scenarios where it would behave as value type. Can you?View
@supercat: I don't understand precisely what you mean by "given a boxed instance of". There are two storages involved and they have different types. The storage that contains the reference to the box is, say, a storage of type object. The reference refers to some storage location; that storage location is logically a variable of value type. (That as an implementation detail there is other stuff nearby in memory, like a vtable, and so on, is an implementation detail.) I'm not following your train of thought here.Leavis
@supercat: But more specifically, the answer to your question is an unqualified yes. If you have a reference to object, and you want to know whether that object is a boxed List<string>.Enumerator then unbox it and you'll know. If the unboxing succeeds, it is; if it fails, then it is not. That's clearly different behaviour. But I think I am still not following your point.Leavis
@EricLippert: If a routine that takes a parameter of type IEnumerator, would there be any way, without using Reflection, to determine whether a passed-in instance was a class type or a boxed structure type, if one didn't know exactly what structure type it might be? I believe that in all scenarios not involving Reflection, dynamic, etc. the behavior of a boxed structure type stored in a location of type IEnumerator<String> will be indistinguishable from that of a class-type object stored likewise. In what regard, if any, would such a struct instance not behave as a class object?View
@supercat: Sure, it would behave like a reference. Since you passed a reference to the method, that's unsurprising! The whole point of interfaces is to enable the building of abstractions so that you don't have to care about the implementation details. But if you're going to box your value types so that you can treat them as classes, then why not simply make them classes in the first place and skip the expense of boxing?Leavis
@EricLippert: Also, I found the quote I was looking for, from msdn.microsoft.com/en-us/library/34yytbws%28VS.85%29.aspx which reads, "For each value type, the runtime supplies a corresponding boxed type, which is a class that has the same state and behavior as the value type." To me, that says if I define a struct foo, the system will create a 'boxed foo' class with the same fields and methods as my struct.View
@supercat: That is certainly not how I would have chosen to describe the feature. That seems to be describing an implementation detail, and the way it describes it is contrary to the C# specification. The spec says: The actual process of boxing a value of a non-nullable-value-type is best explained by imagining the existence of a generic boxing class ... A boxing class like Box<T> above doesn’t actually exist and the dynamic type of a boxed value isn’t actually a class type. Thanks for bringing this to my attention; I will have a talk with the documentation manager.Leavis
@EricLippert that description comes from the CLR spec 8.2.4 "Boxing and unboxing of values" ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdfRationalism
@EricLippert also note the table in section 8.2.2 which describes object as "Object or boxed value type".Rationalism
@EricLippert: I think the .net type-system design is pretty good, given the constraint that it must be possible to zero-initialize or bit-copy all types of fields. It's better than Java's, not "in spite of" the fact that value types have different semantics from heap objects, but in significant measure because of that. In many cases, the unified-type-system abstraction is convenient, but to use it safely one should understand the ways in which it is "leaky".View
@phoog: You are right in pointing out that only "concrete" types derive from System.Object. Interesting link to Eric's blog!Brigittebriley
@OlivierJacot-Descombes: There are three contexts in which type "names" may be used: as storage locations or generic param types, object instances, and generic constraints. Class type "names" have essentially the same meaning in all three cases. Struct type names have different meanings when applied to instances vs other uses; interface have different meanings when applied to constraints vs other uses. Value types effectively inherit from Object only when used to describe heap instances. Interfaces effectively inherit from Object except when used as constraints.View
@supercat: Value types always logically derive from Object. This has nothing to do with the heap or with boxing. The heap is just an implementation detail.Brigittebriley
@EricLippert How much of this comment thread do you think could be deleted? It's getting rather long and I'd like to capture the relevant information in the question/answer.Declan
@View How much of this comment thread do you think could be deleted? It's getting rather long and I'd like to capture the relevant information in the question/answer.Declan
@OlivierJacot-Descombes How much of this comment thread do you think could be deleted? It's getting rather long and I'd like to capture the relevant information in the question/answer.Declan
@casperOne: There are different threads (or topics) here. You could logically group the comments by thread (or topic) and keep them all.Brigittebriley
@OlivierJacot-Descombes Unfortunately, that's not really an option, if they are separate topics, then they shouldn't be attached to the question (might I recommend chat?). Right now, the comments contain multiple extended discussion which is not what the purpose of comments is for.Declan
@View "Value types effectively inherit from Object only when used to describe heap instances" not true. pray do tell how 1.ToString(); works. It never boxes.Forrester
@supercat. If that's too 'obvious' (the compiler inserts the explicit non virtual call) how's this class Foo<T> { public static void Blah(T t) { t.ToString();} } with Foo<int>.Blah(1); I assure you, no boxing happens and it's a callvirtForrester
@ShuggyCoUk: This is getting off the subject of the original questioner's comment (which mainly relates to the fact that struct values are not "objects" in the sense that .net uses the term, even if C# may use the term differently), but Foo<Int32>.Blah(3) will cause the JITter to generate code for a routine with a static call to Int32.ToString(), Foo<UInt32>.Blah(7u) will make it generate one with a static call to UInt32.ToString(), etc. No virtual dispatch within the routines themselves.View
Well, I learned something new today; I had no idea that the CLR spec had such an odd description of the result of boxing. The C# spec description seems much more sensible to me, but at least I understand the source of the confusion now! Thanks all for an interesting discussion.Leavis
@EricLippert that part of the CLR spec was also cited in comments to your answer of just over a month ago: https://mcmap.net/q/356132/-does-unboxing-occur-when-a-class-39-s-value-type-member-is-referencedRationalism
@EricLippert: The CLR spec's description of how value types are implemented may seem "odd", but from what I can tell it accurately describes the way things behave. The notion that "everything is an object" may seem convenient, and C# may endeavor to uphold such an abstraction, but CLR is not Java. Value types exist and they have semantics which usefully differ from objects. There are deficiencies in the CLR handling of value types, some of which can cause confusion, but rather than denouncing anything which differs from "object" behavior as "evil", one should work to fix those deficiencies.View
@EricLippert: Much confusion involving mutable structs, for example, could be easily resolved by defining attributes to indicate whether methods/properties might modify this, and forbidding use of this-mutating methods/properties on values. The assumption that property setters will modify this but other methods won't is often correct, but there are exceptions both ways; allowing attributes to clear up such exceptions would be helpful.View
T
2

Yes, structs inherit from ValueType, and are passed by value. This is true for primitive types as well - int, double, bool, etc (but not string). Strings, arrays and all classes are reference types, and are passed by reference.

If you want to pass a struct by ref, using the ref keyword:

public void MyMethod (ref Vector2 position)

which will pass the struct by-ref, and allow you to modify its members.

Tattler answered 12/2, 2012 at 18:55 Comment(10)
You will only be able to modify its members if it is mutable. Mutable structs are generally discouraged. Furthermore, it is not possible to pass a property by reference.Rationalism
That's true, but note the actual question in the OP: what is passed by ref, what is passed by val.Tattler
@phoog: You can modify the members of a struct passed as ref regardless of whether the struct is supposedly "immutable". For example, void Blah(ref KeyValuePair<int,int> z){z = new KeyValuePair<int,int>(3,4);}View
@supercat, that is not modifying a member of the struct, that's assigning a new value to the (entire) struct.Rationalism
@phoog: It's modifying all members of the struct, in rapid succession, though not necessarily simultaneously. For example, suppose a class has a field Boz of type KeyValuePair<String, String>, initially set to ("James","Stewart") and one thread performs Boz.ToString()` while another thread performs Boz = new KeyValuePair<String,String>("Donna","Reed"). If KeyValuePair` were really immutable, there should be no way for the first thread to output "[James, Reed]"; in fact, though, the second thread will overwrite the fields of the very thing performing ToString.View
@View what if the struct has 2 members of 16 bits each? Then the write is atomic, and what you say couldn't happen. Non-atomic writes do not imply mutability. Put another way: you are describing a threading scenario in which a thread reads a value in an invalid state, and claiming that it is mutation of an immutable struct. It's not mutation, it's logically invalid data. What if it were a 64-bit integer? The analogous error could happen; does that make Int64 mutable? No, it makes it non-atomic.Rationalism
@phoog: An Int64 isn't really immutable. Int64[] l[]={0x1111004444001111}; System.Buffer.BlockCopy(l, 0, l, 3, 2); I would describe the second statement as mutating the instance of Int64 stored in l[0]. Another thread could use BlockCopy to update some other byte simultaneously, and the two writes would be guaranteed not to interfere--something which could not be guaranteed if the former BlockCopy were creating and storing a new instance of Int64. Besides, the same behavior would be observable with KeyValuePair<Int16,Int16> even if it were written atomically.View
@View Nothing is immutable except ROM if you want to look at it that way. If you go outside the type system with BlockCopy to modify data, that doesn't tell you anything about how data behaves within the type system. How do you propose that KeyValuePair<Int16, Int16> would allow a thread to read (1, 4) when another thread is overwriting (1, 2) with (3, 4)? The write is atomic!Rationalism
@phoog: Going outside the type system would imply unsafe code. The behavior of BlockCopy is defined within the type system (except possibly for byte-ordering, so my example was byte-order agnostic). As for the 32-bit struct, it doesn't matter if the write is atomic if it can occur between the time the other thread reads the first half and the time it reads the second (e.g. while the struct's ToString() method is calling ToString() on the first element).View
let us continue this discussion in chatRationalism
S
1

Objects are passed by reference and structs by value. But note that you have the "out" and "ref" modifiers on arguments.

So you can pass a struct by reference like so:

public void fooBar( ref Vector2 position )
{
}
Subset answered 12/2, 2012 at 18:54 Comment(0)
V
0

Every storage location of a structure type holds all the fields, private and public, of that struct. Passing a parameter of a structure type entails allocating space for that structure on the stack and copying all of the fields from the structure to the stack.

With regard to working with structures stored within a collection, using mutable structs with the existing collections generally requires reading out a struct to a local copy, mutating that copy, and the storing it back. Assuming MyList is a List<Point>, and one wants to add some local variable z to MyList[3].X:

  Point temp = MyList[3];
  temp.X += Z;
  MyList[3] = temp;

This is mildly annoying, but is often cleaner, safer, and more efficient than using immutable structs, way more efficient than immutable class objects, and way safer than using mutable class objects. I'd really like to see compiler support for a better way for collections to expose value-type elements. There are ways of writing efficient code to handle such exposure with good semantics (e.g. a collection object could react when elements were updated, without requiring those elements to have any link to the collection) but the code reads horribly. Adding compiler support in a manner conceptually similar to closures would allow efficient code to also be readable.

Note that contrary to what some people claim, a structure is fundamentally different from a class-type object, but for every structure type there is a corresponding type, sometimes referred to as a "boxed structure", which derives from Object (see the CLI (Common Language Infrastructure) specification, sections 8.2.4 and 8.9.7). Although the compiler will implicitly convert any struct into its corresponding boxed type when necessary to pass it to code that expects a reference to a class-type object, will allow references to boxed structs to have their contents copied into real structs, and will sometimes allow code to work with boxed structs directly, boxed structs behave like class objects, because that's what they are.

View answered 12/2, 2012 at 22:46 Comment(17)
As Eric Lippert has pointed out, your terminology is not standard (e.g. "object type" should be "reference type"). In addition, in the context of C# (as opposed to .NET in general), you really don't ever need to think about boxed types. Values of value type undergo a boxing conversion if they are stored in variables of reference type (e.g. System.ValueType, System.Enum, or System.Object), but the fact that this involves a transient boxed type when it comes to the IL type system is an irrelevant implementation detail when it comes to understanding the behavior of C# code.Rations
@Rations while it may not be necessary to think about the implementation details of boxed types, you do need to think somewhat about their semantics. You wouldn't be able to teach c# programming without introducing the concept of boxed types, for example. How else would you explain that (short)(object)1 throws an InvalidCastException?Rationalism
In this example, you don't even need the concept of boxing, not to mention boxed types, to describe what's going on. (short)(object)1 throws an exception because (object)1 is an object containing a value of runtime type int, not a value of runtime type short. This is analogous to why (short)(object)"test", or (string)(object)1, or (System.Attribute)(object)"test" throw exceptions. Boxing doesn't really have anything to do with this behavior.Rations
In general, the concept of boxing (i.e. storing a value of value type in a storage location of reference type) is most relevant when explaining the behavior of mutable structs. Boxed types (i.e. the IL verification types that correspond to each value type), on the other hand, are really never needed to describe how C# works. The fact that the verifier knows that int is boxed to a boxed int on route to being stored as an object is an irrelevant implementation detail.Rations
The terminology gets tricky here, because I think it's accurate to say that (object)1 is a boxed int (that is, an int value which has been boxed). However, it's not accurate to say that it's an instance of type boxed int; it's an instance of type object. The boxed types never appear as storage locations, so they really don't come into play at the C# level.Rations
@kvb: I consider "implementation details" to be things which do not affect behavior in any standard-defined behavior. If one has two variables of generic type T:IEnumerator, and one copies the first to the second, the standard specifies that in some cases it must copy the state of the enumerator, and in other cases the two variables must alias the same state. The question of whether the state gets copied is not an "implementation detail"--it's standards-defined behavior.View
@kvb: (object)1 is stored as a heap object of type System.Int32, i.e. a boxed integer. This may be confirmed by calling GetType() on it. When GetType() returns System.Int32, that means it was called on a boxed instance of Int32; even if one calls 5.GetType(), the system boxes the 5 before calling GetType(), so GetType() is still called on a boxed instance of Int32.View
Regarding implementation details, I'm not sure what you mean. My comment on not needing the concept of boxing for the example was meant to be directed at phoog, if that's the cause of any confusion...Rations
Regarding your latest comment, I think I agree with you, but this is where the terminology gets confusing. (object)1 is a "boxed" integer (an integer that has been boxed), but is not a "boxed integer" (an instance of the "boxed integer" verification type) - the type of the storage location or temporary is object, and the runtime type of the instance is Int32, as you point out. As the ECMA CLI spec points out, boxed types are transient and only ever occur on the evaluation stack; they are really only needed to understand the whether a sequence of IL instructions is valid.Rations
However, in looking at the CLI spec again, I realize that the terminology is a bit muddled and I owe you an apology - they do use the term "object type".Rations
@kvb: Storage locations of 'boxed type' may only exist as a transient concept, but casting a struct-type value to Object generates a persistent instance of an object whose class derives from System.ValueType and will be described by the same Type object that described the storage location holding the original value. I added a link to the CLI spec; sections 8.9.7 and 8.6.1.5 are interesting. How do you like my edits above?View
That's true, and I think at the IL level your description is fine.Rations
@kvb: Is there anything you'd change in my answer? It doesn't seem to be very popular. I wonder some people's real objection to mutable value types is that, to the extent that the CLS spec and behavior are inconsistent with the C# abstraction of all types being inherited from Object, they view the former as defective, and scorn anything that would contradict the Java-style "everything is Object" view.View
@kvb: The mutable value semantics in .net are unfortunately rather limited by the requirements that all value objects must be blittable, and must accept zero-initialization as an acceptable default state. They are further constrained by the inability to declare methods as accepting this by value, ref, or const-ref (note that being able to declare methods on heap-ref types as accepting this by ref would allow immutable heap-ref types to better mimic value semantics). Despite their problems, though, I still consider them 'useful'.View
Regarding the popularity of your answer, it may not be that the content is incorrect, but that it doesn't seem to directly address the issues brought up in the question. For example, your workaround is useful, but you don't explain why it's necessary in any detail, and your last paragraph discusses boxing which isn't directly related to the question. The OP's confusion seems to stem from fairly complete unfamiliarity with the semantics of value types, and I'm not sure that your answer is targetted at a level that he/she would understand.Rations
@kvb: I was attempting to tailor my answer toward some of the OP's confusion relayed in his comments to other questions; I can see where it could appear somewhat lacking with regard to the question above it. The workaround, though, seems directly targeted toward what the OP is asking, and immediately above the three lines of code, I describe the three steps represented thereby: read out a struct, mutate it, and write it back. With regard to the last paragraph, I want to steer any readers away from the philosophy that...View
...everything should be thought of as behaving like Object, except for some things which are evil and defective, and instead encourage people to recognize that they should be aware of types other than Object, and should learn how such types work and use them when it's appropriate to do so.View

© 2022 - 2024 — McMap. All rights reserved.