lunes, 21 de abril de 2014

Events, delegates and functions as arguments in C#

Last year I learnt a bit of Scala and I realized that functional languages have, I have to admit, a lot of pros. I am not saying that the functional paradigm is one of my favorites; it is not, in fact I think that it is one of the most difficult to interiorize. However, some of the ideas of this paradigm are really useful and they would be very useful in other languages.

And suddenly, learning C# I discovered the delegates and how to pass functions as arguments in other functions. So simple, so easy, so clean, so beautiful that I had to share it with you.

Example of delegates in C#:
using System;
namespace DelegatesBlog {
delegate void TypeOnKeyPressed(ConsoleKey key);
class MainClass {
public static void Main(string[] args) {
Console.WriteLine("Start!");
MyKeyCatcher catcher = new MyKeyCatcher();
// Add event handlers
catcher.OnKeyPressedCallback += OhMyPress;
catcher.Start();
}
private static void OhMyPress(ConsoleKey key) {
Console.WriteLine("\nPressed " + key.ToString());
}
}
class MyKeyCatcher {
// The "event" keyword is not mandatory but
// it prevents from being set to null from outside
public event TypeOnKeyPressed OnKeyPressedCallback;
public void Start() {
bool loop = true;
do {
ConsoleKeyInfo info = Console.ReadKey();
OnKeyPressedCallback(info.Key);
loop = info.Key != ConsoleKey.Escape;
} while (loop);
}
}
}
view raw DelegatesC#.cs hosted with ❤ by GitHub
You can also use an anonymous method or a lambda expression instead of an actual method:
catcher.OnKeyPressedCallback += delegate(ConsoleKey key) {
Console.WriteLine("\nPressed from anonymous: " + key.ToString());
};
catcher.OnKeyPressedCallback += ( key => Console.WriteLine("\nPressed from lambda expression: " + key.ToString()) );
In this example with multiple classes subscribing to a delegate, we have a class that allows the user to make movements with the arrow keys (for example, to escape from a maze) and another class that constraints the number of movements to escape:
using System;
namespace DelegatesBlog {
delegate void TypeOnKeyPressed(ConsoleKey key);
class MainClass {
public static void Main(string[] args) {
Console.WriteLine("--Start catching--");
MyKeyCatcher catcher = new MyKeyCatcher();
// Create listeners
new SubscriberClassArrows(catcher);
new SubscriberClassMaxKeyPressed(catcher, 10);
catcher.Start();
}
}
class MyKeyCatcher {
// The "event" keyword is not mandatory but
// it prevents from being set to null from outside
public event TypeOnKeyPressed OnKeyPressedCallback;
private bool loop;
public MyKeyCatcher() {
loop = true;
}
public void Start() {
while (loop) {
ConsoleKeyInfo info = Console.ReadKey();
if (info.Key == ConsoleKey.Escape) {
Stop();
return;
}
OnKeyPressedCallback(info.Key);
}
}
public void Stop() {
loop = false;
Console.WriteLine("--Stop catching--");
}
}
class SubscriberClassArrows {
// Let's say we have a player and we want to move it
// around the scene to get out of the maze
public SubscriberClassArrows(MyKeyCatcher catcher) {
catcher.OnKeyPressedCallback += OnKeyPressed;
}
private void OnKeyPressed(ConsoleKey key) {
switch (key) {
case ConsoleKey.UpArrow:
Console.WriteLine("\n(SubscriberClassArrows ->) Move up");
break;
case ConsoleKey.DownArrow:
Console.WriteLine("\n(SubscriberClassArrows ->) Move down");
break;
case ConsoleKey.LeftArrow:
Console.WriteLine("\n(SubscriberClassArrows ->) Move left");
break;
case ConsoleKey.RightArrow:
Console.WriteLine("\n(SubscriberClassArrows ->) Move right");
break;
default:
break;
}
}
}
class SubscriberClassMaxKeyPressed {
// Let's say we have max number of steps to get out of the maze
private int leftMovements;
private MyKeyCatcher catcher;
public SubscriberClassMaxKeyPressed(MyKeyCatcher catcher, int maxKey) {
leftMovements = maxKey;
this.catcher = catcher;
catcher.OnKeyPressedCallback += OnKeyPressed;
}
private void OnKeyPressed(ConsoleKey key) {
leftMovements--;
Console.WriteLine("\n(SubscriberClassMaxKeyPressed ->) Left movements to end " + leftMovements);
if (leftMovements <= 0) {
catcher.Stop();
}
}
}
}
Instead of defining a new delegate each time, you can take advantage of the delegates that come with the .Net library which are Action and Func. The first one is used when the delegate returns void and the other when a value is returned (thus not common with events). Both are overloaded for different number of arguments, from 0 up to 16 (I think it's enough), with the form: Action(T1 arg1, T2 arg2, ...).
using System;
namespace DelegatesBlog {
// Not needed anymore!
//delegate void TypeOnKeyPressed(ConsoleKey key);
class MainClass {
// Nothing changes
...
}
class MyKeyCatcher {
// Use action instead of our own delegate type
//public event TypeOnKeyPressed OnKeyPressedCallback;
public event Action<ConsoleKey> OnKeyPressedCallback;
...
}
class SubscriberClassArrows {
// Nothing changes
...
}
class SubscriberClassMaxKeyPressed {
// Nothing changes
...
}
}
Passing function as arguments to other functions is also very easy. Here I modify the example to prompt the user to retry the game when the max movements are reached:
using System;
namespace DelegatesBlog {
class MainClass {
// Nothing changes
...
}
class MyKeyCatcher {
...
public void Stop() {
loop = false;
Console.WriteLine("--Stop catching--");
}
// Overload Stop, If the stop is cancelled can cancel the stop
public void Stop(Func<bool> cancel) {
if (!cancel()) {
Stop();
}
}
}
class SubscriberClassArrows {
// Nothing changes
...
}
class SubscriberClassMaxKeyPressed {
...
// When reach the max movements prompt to retry
// If yes, reset the max
private void OnKeyPressed(ConsoleKey key) {
leftMovements--;
Console.WriteLine("\n(SubscriberClassMaxKeyPressed ->) Left movements to end " + leftMovements);
if (leftMovements <= 0) {
// A lambda expression for prompting
catcher.Stop(() => {
Console.WriteLine("Do you want to try again? (if yes press Y)");
if (Console.ReadKey().Key == ConsoleKey.Y) {
Console.WriteLine();
leftMovements = 10;
return true;
}
return false;
});
}
}
}
}
Note that when you use delegates in Unity3D and you apply them to a MonoBehaviour, you must be careful with their lifetime and prevent the delegate from calling methods that are not anymore available. Be aware that changing the scene or removing the component from the game object, removes the script from memory but doesn't deallocate the callback, so you should remove it explicitly in the onDestroy method.
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
void Start() {
print("Start. Init the delegate callback");
catcher.OnKeyPressedCallback += OnKeyPressed;
}
void OnDestroy() {
print("Script was destroyed. Remove Delegate");
catcher.OnKeyPressedCallback -= OnKeyPressed;
}
private void OnKeyPressed(ConsoleKey key) {
print("Callback");
...
}
}

Enjoy!

jueves, 17 de abril de 2014

New challenges

New job, new developing tools, new projects, new versions of frameworks, ... What does it mean? More fun... well, sometimes ;-)

Since November I am working in a game company where we work with Unity3D, C# and all the surrounding tools and languages around such big project. It was completely new for me to change to a three dimensional world, but it's really worth it and I discovered that C# is a great language. I will definitely write about it.

In addition, I decided to start my own project with my sister, a 2d casual game. However, the new 2d Unity implementation does not suit all my needs and it only gives me headaches. Thus, I decided to start the project using Cocos2d v3.
I haven't progressed too much with the project but up to this point I am very happy with the improvements I've already seen in the framework. But it is quite recent, which means no documentation, no questions in stackoverflow, not many places to look at when problems arise... but this is an interesting challenge and I hope I can contribute with my findings in this blog.

This is plainly an update post, and a way to push myself into writing about these new topics.