Are structs 'pass-by-value'?

前端 未结 9 1342
轮回少年
轮回少年 2020-11-30 23:43

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;         


        
相关标签:
9条回答
  • 2020-11-30 23:59

    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.

    0 讨论(0)
  • 2020-12-01 00:01

    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 
    
    0 讨论(0)
  • 2020-12-01 00:03

    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.

    0 讨论(0)
  • 2020-12-01 00:04

    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:

    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.

    0 讨论(0)
  • 2020-12-01 00:06

    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 )
    {
    }
    
    0 讨论(0)
  • 2020-12-01 00:11

    .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.

    0 讨论(0)
提交回复
热议问题