Monday, 18 May 2015

Types and Passing Parameters

Types

Types are either value types or reference types.

Value Types

A variable is created on the stack and value is held in that variable itself.  Value types are typically small as you don't want huge amounts of stack memory used.  Value types are immutable (but can be overwritten, so an int variable can hold a 4 and that be overwritten with 6).  Aim for <16 bytes of memory used.  Assigning to value types makes an expensive copy operation.

Reference Types

A reference type sees memory used in both the stack and the heap.  Memory is allocated on the heap to hold all the data stored in an instance of a reference type.   (This may be a large amount of memory).  A variable is also created on the stack.  This stack variable holds a pointer to the location on the heap where the actual instance data is held.

Multiple reference variables can reference the same object.
Single variable can point to multiple objects over its lifetime
Objects are allocated on the heap by the new operator

Classes are reference types (as they may be large).
Structs and enums are value types.
(Structs are sealed so you can't inherit from them, nor can you inherit from a base class; They all inherit from System.Object).

Employee e1  = new Employee();
Employee e2;
e2  = e1;

Using either variable to update the object works ok.



String is a special case - Strings are reference types but they act like Value types.

Passing variables is by default pass by value - that is a copy / new instance of variables is created in the called method.  This copy is amended and destroyed once the called method has completed.  A variable of the same name in the "parent" method is unaffected.  This is true even for ref  types.


Passing Parameters

Parameters pass "by value"
Ref types pass a copy of the reference
Value types pass a copy of the value.  Changes to the value don't propagate to the caller

ref and out are used to pass "by reference"
ref parameters require initialized variable (because it will be used by the caller and hence needs to be initialized by the caller).

Invoice invoice = new Invoice();
invoice.ID = 1;
int value = 1;

DoWork(invoice, value);
Assert.IsTrue(invoice.ID == 5);
Assert.IsTrue(value == 1);

void DoWork(Invoice invoice, int value)
{
   invoice.ID = 5;
   value = 3;
}

The above works because when DoWork is called you get a copy of the variable invoice, i.e. a second pointer to the same object on the heap.  Setting the ID via the second pointer affects the memory which is still pointed to by the calling code.  Hence the first Assert.IsTrue passes ok.

But you didn't truly work on the variable in the initial calling code.  You got a copy and that copy pointed to the same object/memory location.  The following would cause the first Assert to fail

void DoWork(Invoice invoice, int value)
{
   invoice = new Invoice();
   invoice.ID = 5;
   value = 3;
}

It fails because you are working on a copy in DoWork.


If you really want to work with that variable, use pass by ref.  Add the ref keyword into both the call and the method signature.  This means you pass a reference to the reference, a reference to the initial variable Invoice (which still points at the same object on the heap).  This also works for value types; you'd in effect pass a ref to the value variable (which holds the value).

public bool DoWork(ref Invoice invoice, out int value)
{
       invoice.ID = 5;
       value++;
       return (value == 2);
}

The ref and out keywords both mean pass by reference.  The difference being that ref parameters must be initialized outside the method as they will be used in the method.  out methods do not need to be initialized.  out alerts the compiler that the value may not be used within the method.  Rather we will be storing the result of some operation in this parameter for returning to the calling method.




No comments:

Post a Comment