C# is NOT C++ in any way shape or form.
C# is more inspired by Java than C++, and it bars you from memory management in many ways (though there are small commands to give you some lower level access).
And thereâs a reason for this. Pointers donât exactly exist in C#, or .Net for that matter, because all the memory is managed for you. This managed memory is called the âheapâ and itâs a large swath of memory that objects are placed on. Problem is, if memory starts getting filled up, the memory manager can reorganize the objects on the heap to basically âdefragâ it so it can clear up memory from the gaps. Because of this, the actual âpointerâ to the object can move sporadically without you knowing. So instead of using pointers in C#, you use managed references.
Managed references are inherit to a datatype. All classes are reference types, structs are not.
Structs too have their own weird pointer, thatâs not a pointer, and that is with the ârefâ and âoutâ parameter modifiers which act as pointers to the stack, because the memory location theyâre pointing at is always deeper in the stack from the local scope. The difference between them being ref is just a general pointer to the value in memory, out forces the called method to set that value to something.
ref and out CAN be used for classes as well, if you plan to return a reference to an object. Take for example a âDictionary.TryGetValueâ and âDictionary.Itemâ.
When attempting to get a TValue based on a TKey, if you attempted this with the normal accessor and the TKey doesnât exist it throws an exception:
But with TryGetValue you include an out parameter. If the TKey exists, the out parameter is set, and it returns true. If it doesnât exist, the out parameter is set to default(TValue) and false is returned:
Anyways,
Like Java, C# is intended to be object-oriented by design. Unlike C++ which is C with classes tacked on to offer object-oriented design. Of course as time has progressed, C# has added some extra bells and whistles that Java designers probably wouldnât consider âpure OOâ⌠but no matter what, at the end of the day, C# is rooted in the concept of everything being an âobjectâ.
Which leads to:
Really the compiler interprets this as:
myMultiDelegate = Delegate.Combine(myMultiDelegate, new MultiDelegate(addressof PowerUp));
A delegate is supposed to replace functionality like that of the âvoid pointerâ in C++. A void pointer just allows storing the memory address of anything. Problem is void pointers can lead to a lot of unsafe nonsense.
Furthermore, we need to remember how design principles in C# are based on the idea that pointers arenât used, but rather references, because memory moves around. Now of course, function locations donât really move (well, in theory⌠remember that types can be compiled at runtime in the JIT, and as C# matured other features were implemented that toss this on its head). So design wise, C# approached it where instead of having a pointer to a function, you have an object representation of a function.
This is the delegate.
A delegate has 2 parts.
Definition:
public delegate void Foo();
We give the type a name, and a shape (the shape of this has a void return, and no params).
Objects:
Foo d = new Foo(this.SomeFunctionWithShapeFoo);
//shorthanded as
Foo d = this.SomeFunctionWithShapeFoo;
//or
d += this.SomeOtherFunctionWithShapeFoo;
Now we have object identity, and interface definition, for a function pointer.
Note! the lack of () at the end. The parens state youâre calling the function, no parens state youâre getting the address of the funciton.
Furthermore, C# builds in the idea that a delegate can store multiple references to functions. Itâs sort of an array of delegates, which is shaped as a delegate. This is more or less to support the âeventâ functionality, which Iâm not going to get into.
Now,
With everything being an object, including functions, you can get more paradigms. Such as some functional paradigm like stuff. Which is where lamdas/anonymous functions come in.
lamdas are just a syntax for anonymous functions to make them cleaner.
Now an anonymous function actually just compiles up into its own data type for you. Itâs sort of âsyntax sugarâ. That records the current state when created.
What I mean isâŚ
void Foo()
{
Random rand = new Random();
Action[] arr = new Action[10]; //action is the delegate System.Action with shape 'void Action()'
for (int i = 0; i < 10; i++)
{
double d = rand.NextDouble();
//this lamda is going to generate a type that contains 'd' as a field member
//that state information will be held and used when 'f' is called in the future
arr[i] = () => { Console.WriteLine(d); };
}
foreach(Action f in arr)
{
//here the various random values are printed out in succession
//note how the state from earlier was preserved
f();
}
}
These are often known as âclosuresâ in languages like python or lisp (hence the lamda notation, which is borrowed from lisp). They close around the current state at the time of creation.
This allows you to write code that is called âfunctionalâ because it resembles âfunctionsâ in the mathematical sense.
f(x) = 3x + 2
g(x) = x^2 + 1
f(g(x)) = 3(x^2 + 1) + 2
SO, with the ExecuteEvents.Execute, if you want to call a function on the targets with some parameters youâd use this closure to carry through the methods.
ExecuteEvents.Execute<RegisteredBingoMessages>(m_gameobjBingo, null, (c,e) => { c.ChangeAutoSpin(m_bAutospin) });
Now, Execute is given a closure that has the current state of the calling code (it has m_bAutoSpin included). The closure has the shape of the delegate EventFunction which is like:
(T c, BaseEventData e)
The method has 2 params, first is of type T (RegisteredBingoMessages), and the other of type BaseEventData (because all EventSystem is used primarily for the EventSystem which is used primarily for mouse/touch input, which includes event info along with it). Note that I passed in null for the BaseEventData, because we donât need it. You may wonder why the types arenât declared in code, thatâs just cause the compiler infers them. You could declare them explicitly:
ExecuteEvents.Execute<RegisteredBingoMessages>(m_gameobjBingo, null, (T c,BaseEventData e) => { c.ChangeAutoSpin(m_bAutospin) });
Execute can now call the passed in closure and you can use m_bAutoSpin on the âcâ that is called. This can be several of them, because the GameObject might have multiple components that implement RegisteredBingoMessages.[/code]