Dela via


Delegater och lambdas

Ett ombud definierar en typ som representerar referenser till metoder som har en viss parameterlista och returtyp. En metod (statisk eller instans) vars parameterlista och returtyp matchar kan tilldelas till en variabel av den typen, anropas sedan direkt (med lämpliga argument) eller skickas som ett argument till en annan metod och anropas sedan. I följande exempel visas ombudsanvändning.

using System;
using System.Linq;

public class Program
{
    public delegate string Reverse(string s);

    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Reverse rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}
  • Raden public delegate string Reverse(string s); skapar en ombudstyp för en metod som tar en strängparameter och sedan returnerar en strängparameter.
  • Metoden static string ReverseString(string s) , som har exakt samma parameterlista och returtyp som den definierade delegattypen, implementerar ombudet.
  • Raden Reverse rev = ReverseString; visar att du kan tilldela en metod till en variabel av motsvarande ombudstyp.
  • Raden Console.WriteLine(rev("a string")); visar hur du använder en variabel av en delegattyp för att anropa ombudet.

För att effektivisera utvecklingsprocessen innehåller .NET en uppsättning ombudstyper som programmerare kan återanvända och inte behöver skapa nya typer. Dessa typer är Func<>, Action<> och Predicate<>, och de kan användas utan att behöva definiera nya ombudstyper. Det finns vissa skillnader mellan de tre typerna som har att göra med hur de var avsedda att användas:

  • Action<> används när det finns ett behov av att utföra en åtgärd med hjälp av ombudets argument. Metoden som den kapslar in returnerar inte något värde.
  • Func<> används vanligtvis när du har en transformering till hands, dvs. du måste omvandla ombudets argument till ett annat resultat. Projektioner är ett bra exempel. Metoden som den kapslar in returnerar ett angivet värde.
  • Predicate<> används när du behöver avgöra om argumentet uppfyller ombudets villkor. Det kan också skrivas som en Func<T, bool>, vilket innebär att metoden returnerar ett booleskt värde.

Nu kan vi ta vårt exempel ovan och skriva om det med hjälp av ombudet Func<> i stället för en anpassad typ. Programmet fortsätter att köras på exakt samma sätt.

using System;
using System.Linq;

public class Program
{
    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Func<string, string> rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}

I det här enkla exemplet verkar det lite överflödigt att ha en metod som definierats utanför Main metoden. .NET Framework 2.0 introducerade begreppet anonyma ombud, vilket gör att du kan skapa "infogade" ombud utan att behöva ange någon ytterligare typ eller metod.

I följande exempel filtrerar ett anonymt ombud en lista till bara de jämna talen och skriver sedan ut dem till konsolen.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(
          delegate (int no)
          {
              return (no % 2 == 0);
          }
        );

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

Som du ser är ombudets brödtext bara en uppsättning uttryck, som alla andra ombud. Men i stället för att det är en separat definition har vi introducerat den ad hoc i vårt anrop till List<T>.FindAll metoden.

Men även med den här metoden finns det fortfarande mycket kod som vi kan kasta bort. Det är här lambda-uttryck spelar in. Lambda-uttryck, eller bara "lambdas" för kort, introducerades i C# 3.0 som en av de viktigaste byggstenarna i Language Integrated Query (LINQ). De är bara en mer praktisk syntax för att använda ombud. De deklarerar en parameterlista och metodtext, men har ingen egen formell identitet, såvida de inte har tilldelats till ett ombud. Till skillnad från ombud kan de tilldelas direkt som den högra sidan av händelseregistreringen eller i olika LINQ-satser och metoder.

Eftersom ett lambda-uttryck bara är ett annat sätt att ange ett ombud bör vi kunna skriva om exemplet ovan för att använda ett lambda-uttryck i stället för ett anonymt ombud.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(i => i % 2 == 0);

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

I föregående exempel är i => i % 2 == 0det lambda-uttryck som används . Återigen är det bara en praktisk syntax för att använda ombud. Vad som händer under täcket liknar vad som händer med den anonyma delegaten.

Återigen är lambdas bara ombud, vilket innebär att de kan användas som händelsehanterare utan problem, som följande kodfragment illustrerar.

public MainWindow()
{
    InitializeComponent();

    Loaded += (o, e) =>
    {
        this.Title = "Loaded";
    };
}

Operatorn += i den här kontexten används för att prenumerera på en händelse. Mer information finns i Så här prenumererar du på och avbryter prenumerationen på händelser.

Ytterligare läsning och resurser