Search code examples
c#syntaxoperator-keywordcalling-convention

What is the C# equivalent to C++’s “&param” in a method signature?


Hey in C++ it is possible to have &Operator in Signature for example:

void Test(GameVector &vecInfo)
{
    GameVector = &vecInfo;
    ....
}

Are you able to do the same in C#? How would that look like?


Solution

  • The semantics of parameter passing are greatly influenced by the two kinds of types in the. NET world, ValueTypes and Reference Types. You need to get a good understanding of what they mean before you can understand things likeref.

    This answer is copied from another question that has since closed (it is my answer). The question was somewhat different, but the answers are very similar.

    Background

    There are two kinds of objects in .NET-land, Reference types and Value types. The main difference between the two is how assignment works.

    Value Types

    When you assign a value type instance to a variable, the value is copied to the variable. The basic numeric types (int, float, double, etc) are all value types. As a result, in this code:

    decimal dec1 = 5.44m;
    decimal dec2 = dec1;
    dec1 = 3.1415m;
    

    both decimal variables (dec and dec2) are wide enough to hold a decimal valued number. In each case, the value is copied. At the end, dec1 == 3.145m and dec2 == 5.44m.

    Nearly all value types are declared as a struct (yes, if you get access to the .NET sources, int is a struct). Like all .NET types, they act (when boxed) as if they are derived from the object base class (their derivation is through System.ValueType. Both object (aka System.Object) and System.ValueType are reference types, even though the unboxed types that derive from System.ValueType are value types (a little magic happens here).

    All value types are sealed/final - you can't sub-class them. You also can't create a default constructor for them - they come with a default constructor that initializes them to their default value. You can create additional constructors (which don't hide the built-in default constructor).

    All enums are value types as well. They inherit from System.Enum but are value types and behave mostly like other value types.

    In general, value types should be designed to be immutable; not all are.

    Reference Types

    Variables of reference types hold references, not values. That said, it sometimes help to think of them holding a value - it's just that that value is a reference to an object on the managed heap.

    When you assign to a variable of reference type, you are assigning the reference. For example:

    public class MyType {
        public int TheValue { get; set; }
        // more properties, fields, methods...
    }
    
    MyType mt1 = new MyType() {TheValue = 5};
    MyType mt2 = mt1;
    mt1.TheValue = 42;
    

    Here, the mt1 and mt2 variables both contain references to the same object. When that object is mutated in the final line of code, you end up with two variables both referring to an object whose TheValue property is 42.

    All types declared as a class are reference types. In general, other than the numeric types, enums and bools, most (but not all) of the types that you normally encounter will be reference types.

    Anything declared to be a delegate or an event are also reference types under the covers. (In an answer to the original question this was posted to...) Someone mentioned interface. There is no such thing as an object typed purely as an interface. Both structs and classes may be declared to implement an interface - it doesn't change their value/reference type nature, but a struct stored in a variable typed as an interface will be boxed.

    Difference in Constructor Behavior

    One other difference between Reference and Value Types is what the new keyword means when constructing a new object. Consider this class and this struct:

    public class CPoint {
        public float X { get; set; }
        public float Y { get; set; }
        public CPoint (float x, float y) {
            X = x;
            Y = y;
        }
    }
    
    public struct SPoint {
        public float X { get; set; }
        public float Y { get; set; }
        public SPoint (float x, float y) {
            X = x;
            Y = y;
        }
    }
    

    They are basically the same, except that CPoint is a class (a reference type) and SPoint is a struct (a value type).

    When you create an instance of SPoint using the two float constructor (remember, it gets a default constructor auto-magically), like this:

    var sp = new SPoint (42.0, 3.14);
    

    What happens is that the constructor runs and creates a value. That value is then copied into the sp variable (which is of type SPoint and large enough to hold a two-float SPoint).

    If I do this:

    var cp = new CPoint (42.0, 3.14);
    

    Something very different happens. First, memory is allocated on the managed heap large enough to hold a CPoint (i.e., enough to hold two floats plus the overhead of the object being a reference type). Then the two-float constructor runs (and that constructor is the only constructor - there is no default constructor (the additional, programmer-written constructor hides the compiler generated default constructor)). The constructor initializes that new CPoint in the memory allocated on the managed heap. Finally, a reference to that newly create object is created and copied to the variable cp.

    Parameter Passing

    Sorry the preamble took so long.

    Unless otherwise specified, all parameters to functions/methods are passed by value. But, don't forget that the value of a variable of reference type is a reference.

    So, if I have a function declared as (MyType is the class declared above):

    public void MyFunction(decimal decValue, MyType myObject) {
        // some code goes here
    }
    

    and some code that looks like:

    decimal dec1 = 5.44m;
    MyType mt1 = new MyType() {TheValue = 5};
    MyFunction (dec1, mt1);
    

    What happens is that the value of dec1 is copied to the function parameter (decValue) and available for use within MyFunction. If someone changes the value of the decValue within the function, no side effects outside the function occurs.

    Similarly, but differently, the value of mt1 is copied to the method parameter myObject. However, that value is reference to a MyType object residing on the managed heap. If, within the method, some code mutates that object (say: myObject.TheValue=666;), then the object to which both the mt1 and myObject variables refer is mutated, and that results in a side effect viewable outside of the function. That said, everything is still being passed by value.

    Passing Parameters by Reference

    This is where your question gets answered

    You can pass parameters by reference in two ways, using either the out or ref keywords. An out parameter does not need to be initialized before the function call (while a ref parameter must be). Within the function, an out parameter must be initialized before the function returns - ref parameters may be initialized, but they do not need to be. The idea is that ref parameters expect to be passed in and out of the function (by reference). But out parameters are designed simply as a way to pass something out of the function (by reference).

    If I declare a function like:

    public void MyByRefFunction(out decimal decValue, ref MyType myObject) {
        decValue = 25.624;    //decValue must be intialized - it's an out parameter
        myObject = new MyType (){TheValue = myObject.TheValue + 2};
    }
    

    and then I call it this way

    decimal dec1;       //note that it's not initalized
    MyType mt1 = new MyType() {TheValue = 5};
    MyType mt2 = mt1;
    MyByRefFunction (out dec1, ref mt1);
    

    After that call, dec1 will contain the value 25.624; that value was passed out of the function by reference.

    Passing reference type variables by reference is more interesting. After the function call, mt1 will no longer refer to the object created with TheValue equal to 5, it will refer to the newly created object with TheValue equal to 5 + 2 (the object created within the function). Now, mt1 and mt2 will refer to different object with different TheValue property values.

    With reference types, when you pass a variable normally, the object you pass it may mutate (and that mutation is visible after the function returns). If you pass a reference by reference, the reference itself may mutate, and the value of the reference may be different after the function returns.