可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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 struct
s) 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:
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
.
回答2:
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.
回答3:
.NET data types are divided into value and reference types. Value types include int
, byte
, and struct
s. 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.
回答4:
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.
回答5:
Just to illustrate the different effects of passing struct vs class through methods:
(note: tested in LINQPad 4)
Example
/// via http://stackoverflow.com/questions/9251608/are-structs-pass-by-value
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
回答6:
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.
回答7:
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 )
{
}
回答8:
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.
回答9:
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.