Delegates, events och lambdas

OBS: Lite överkurs =)

Delegates är ett sätt att kunna göra så att variabler pekar mot metoder istället för mot värden eller objekt i minnet. De är lite överkurs, men ganska användbara.

// Skapar en delegate som passar in på metoder som 
// inte tar emot några parametrar eller returnerar något.
delegate void Task(); 

// En metod som passar in på delegaten
static void SayHello() {
  System.WriteLine("Hello!");
}

static void Main(string[] args)
{
  Task t = SayHello; // Lagra en referens till SayHello-metoden i "t"
  t(); // Att köra t som en metod är nu samma sak som att köra SayHello.
}

När man skapar en delegat så beskriver den en metodprofil. Man kan säga att varje delegat beskriver en kategori av metoder.

// Stämmer in på metoder som tar emot en int-parameter och inte returnerar något.
delegate void DelegateOne(int y);

// Stämmer in på metoder som returnerar en int utan att ta emot några parametrar.
delegate int DelegateTwo();

// Stämmer in på metoder som returnerar en float efter att ha tagit emot
// en string och en int.
delegate float DelegateThree(string x, int y);

När man skapat sin delegat används den alltså som om den vore en datatyp. När man lagrar metoderna i den så ser man till att inte använda () efter metodnamnet, för då körs ju metoden istället, innan tilldelningen.

DelegateOne test = MethodOne;

Action

En Action är en generisk delegat som passar in på metoder som inte returnerar ett värde. Om man vill matcha metoder som tar emot parametrar kan dessa anges mellan <>.

static void Hello()
{
  Console.WriteLine("Hello");
}

static void HelloTo(string target)
{
  Console.WriteLine($"Hello {target}");
}

Action example1 = Hello;
Action<string> example2 = HelloTo

example1();
example2("Micke");

Dictionary med Actions

Ett exempel på hur man kan använda delegater.

Dictionary<string, Action> actions = new Dictionary<string, Action>();

actions.Add("first", DoFirst);
actions.Add("second", DoSecond);
  
actions["first"](); // Kör DoFirst-metoden


static void DoFirst()
{
  /* .. */
}

static void DoSecond()
{
  /* .. */
}

Func

Func är en generisk delegat som matchar metoder som returnerar något. En eller flera datatyper anges inom <>. Anges flera så är den sista alltid returtypen, resten är parametrar.

static int Addition(int a, int b)
{
  return a + b;
}

static string MakeStrengthString(int str)
{
  return $"Strength: {str}";
}

Func<int, int, int> math = Addition;

Func<int, string> statDisplay = MakeStrengthString;

int y = math(3,4);

string strength = statDisplay(14);

Anonyma metoder i delegatvariabler

Anonyma metoder saknar eget namn.

static void Main(string[] args)
{
  Action t = delegate()
    {
      System.WriteLine("Hello!");
    }
    
  t();
}

De är praktiska när man aldrig faktiskt kommer att anropa metoden med dess eget namn, utan bara vill kunna lägga in den i en variabel eller en lista.

Dictionary<string, Action> actions = new Dictionary<string, Action>();

actions.Add("greet", 
  delegate ()
  {
    Console.WriteLine("Hello");
  }
);

actions["greet"]();

Multicasting: delegat-variabler med flera metoder

Om man vill att flera metoder ska köras när en delegat-variabel anropas så kan man kombinera delegater för att skapa s.k. multicast-delegater.

Action multiCaster = delegate() { Console.WriteLine("Hello"); };

multiCaster += delegate() { Console.WriteLine("World"); };

multiCaster(); // Skriver först ut Hello, sedan World
Action good = delegate() { Console.WriteLine("Good"); };

Action bye = delegate() { Console.WriteLine("Bye"); };

Action morning = delegate() { Console.WriteLine("Morning"); };

Action goodMorning = good + morning;
Action goodBye = good + bye;

goodMorning(); // Skriver ut "Good" och "Morning"
goodBye(); // Skriver ut "Good" och "Bye"

Att lägga till en metod till en multicast-delegat kallas subscribing, och att ta bort en metod från en multicast-delegat kallas unsubscribing.

Events

Nackdelen med multicast-delegater är att den som har tillgång till dem inte bara kan lägga till nya metoder i dem, utan också aktivera dem och göra större ändringar – som att till exempel ändra dem till null.

Med en event kan den som har tillgång utifrån bara lägga till och ta bort metoder (subscribe/unsubscribe)

Events kan bara existera i klasser, och de kan bara anropas (invoke) inifrån den klassen

Avatar.cs
public class Avatar
{
  public event Action OnDeath; // OBS: nyckelordet "event"

  public Avatar()
  {
    OnDeath += DeathMessage; // Subscribe:a klassmetoden DeathMessage
  }

  public void Update()
  {
    OnDeath.Invoke(); // Aktivera (invoke) eventet
  }

  public void DeathMessage() { Console.WriteLine("YOU DIED"); }
}
Program.cs
Avatar p = new Avatar();

Action PauseGame = delegate() { Console.WriteLine("Game is paused"); };

// Subscribe:a den lokala metoden PauseGame till eventet OnDeath
p.OnDeath += PauseGame;

p.Update(); // Kör Update-metoden, som i sin tur invoke:ar eventet

Lambdas

Lambda-uttryck är, enkelt uttryckt, ett sätt att skriva väldigt enkla anonyma metoder (anonyma delegater) vars returvärden är direkta resultat av deras parametrar. Ett lambda-uttryck består av en parentes där den anonyma metodens parametervärden anges, en => och slutligen en enkel uträkning som motsvarar det som ska returneras från metoden.

Uträkningen kan bytas ut mot ett kodblock som returnerar ett värde, om mer omfattande

// Delegat som passar alla metoder som tar emot två int-parametrar och
// som returnerar en int som resultat
delegate int Calculation(int x, int y);

static void Main(string[] args)
{
  // Lambda-uttrycket => har två inputs på vänster sida, 
  // och uträkningen på höger sida resulterar i en int.
  // Därför passar lambda-uttrycket in på delegaten Calculation.
  Calculation c = (xInput, xOutput) => xInput * xOutput;

  int result = c(10, 5);
  
  // Detta gör samma sak som ovan, men med ett kodblock istället 
  // för en ren beräkning
  Calculation c2 = (xInput, xOutput) => {return xInput * xOutput};
}

Lambdas används väldigt ofta när man till exempel vill filtrera en lista på något sätt.

List<int> numbers = new List<int>() {2,3,4,5,6};

// FindAll returnerar en lista med alla föremål i listan som matchar ett visst
// kriterium. Kriteriet ska vara utformat som en metod, som tar emot ett
// föremål av rätt datatyp som parameter och returnerar en bool.
// lambda-uttrycket nedan tar emot en input på vänster sida (n) och uttrycket
// på höger sida är en boolsk jämförelse.
// Därför kommer lowNumbers att innehålla en lista med alla integers från
// numbers, som är < 4.
List<int> lowNumbers = numbers.FindAll(n => n < 4);

De kan också användas till events och till multicast-delegates. Ofta används detta när det bara är ganska lite kod som ska köras.

Avatar p = new Avatar();

p.OnDeath += () => Console.WriteLine("Game is paused");

Last updated