Tag den kode og lad den vandre  

Delegates er en af .NET typesystemets mest omtalte features. De bliver ofte omtalt med næsten andægtig ærefrygt i stemmen – men hvad er delegates egentlig for noget? – og hvilken gavn – om nogen – gør de? Disse og mange andre spørgsmål vil blive besvaret i denne sjette udgave af .Henrik om .NET.

Kort og godt...

Et delegate-objekt indeholder referencer til en eller flere metoder. Når delegate-objektet kaldes, kalder det videre til disse metoder.

Knap så kort men en hel del mere uddybende...

Ovenstående ganske kortfattede beskrivelse giver naturligvis ikke hele sandheden om, hvad delegates er, thi så var der jo ikke den store grund til skrive ret meget mere, og i så fald ville det i sandhed være et sølle emne for en artikel.

Et delegate-objekt kan ganske rigtigt indeholde referencer til et antal metoder, men dog ikke alle metoder i hele verdenen. Når man skaber et delegate-objekt, gør man det som med alle andre objekter ud fra en type. Og den delegate-type som delegate-objektet skabes ud fra bestemmer, hvilke metoder delegate-objektet kan referere til – eller pege på om man vil. Når man arbejder med delegates, skal man altså starte med en delegate-type. En delegate-type definerer en familie af metoder bestemt ud fra deres metodesignatur.

Erklæring af en delegate-type

Et eksempel på en delegate-type kunne være:

    Private Delegate Sub MinSimpleDelegateType( _
      ByVal minSimpleParameter As integer, ByVal text As String)
    private delegate void MinSimpleDelegateType(
        int minSimpleParameter, string text);

Delegate-objekter defineret ud fra denne type ("MinSimpleDelegateType"), skal referere til metoder med (stort set) samme metodesignatur. Det vil konkret sige, at metoderne skal være subs (i VB termer) eller voids (i C# termer), at de skal have dels en parameter af typen integer, dels en parameter af typen string – parametre som iøvrigt skal overføres by value. Når metoden kun stort set skal være den samme, så er det fordi, at navnene på metoderne og deres parametre sagtens kan være nogle andre – og typisk er det. Så metoden behøver ikke hedde "MinSimpleDelegateType", og parametrerne behøver ikke hedde "minSimpleParameter" og "text".

Instansiering af et delegate-objekter

Nu er delegate-typen altså defineret, og derved er det blevet bestemt, hvilke metoder delegate-objekter baseret på denne type kan pege på. Dernæst skal der kreeres et delegate-objekt, hvilket kan gøres på flere måder. Den måde, man oftest ser brugt, er følgende:

    Dim del As New MinSimpleDelegateType(AddressOf MinMetode)
    MinSimpleDelegateType del = new MinSimpleDelegateType(MinMetode);	

Den måske mest beskrivende version af ovenstående er nok VB-koden, idet den forholdsvist direkte formulerer, hvad det er, der sker: Det instansierede delegate-objekt kommer til at indeholde en reference (en adresseangivelse) til metoden "MinMetode". Man kan på denne måde lade delegate-objekter referere både statiske/shared metoder såvel som instansmetoder.

Lad os antage, at "MinMetode"-metoden er defineret, som følger

    Private Sub MinMetode(ByVal minParameter As integer, ByVal caption As String)
      System.Windows.Forms.MessageBox.Show( _      
          "Svaret er " + minParameter.ToString(), caption)
    End Sub
    private void MinMetode(int minParameter, string caption) 
    { 
      System.Windows.Forms.MessageBox.Show("Svaret er " + minParameter.ToString(), caption); 
    }

"MinMetode"-metoden opfylder altså dermed kravene til delegate objekter af typen "MinSimpleDelegateType" som beskrevet ovenfor.

Vi har nu defineret en delegate-type ("MinSimpleDelegateType"), og vi har instansieret et delegate-objekt ("del"), der refererer en lokal instans-metode ("MinMetode"). Nu kan vi så kalde "MinMetode"-metoden indirekte ved at kalde delegate-objektet:

    del(42)
    del(42);

I VB kan man selv vælge, om man vil kalde delegate-objektet med syntaksen:

    del(42, "MegetSimpelOgRetTåbeligDelegateDemo")

eller om man hellere vil kalde Invoke-metoden:

    del.Invoke(42, "MegetSimpelOgRetTåbeligDelegateDemo")

I dens helhed ser koden nu ud som som følger:

    Private Delegate Sub MinSimpleDelegateType(ByVal minSimpleParameter As integer ,
        ByVal text As String)

    Private Sub btnMegetSimpelOgRetTåbeligDelegateDemo_Click(ByVal sender As  System.Object,  _
        ByVal e As System.EventArgs) Handles btnMegetSimpelOgRetTåbeligDelegateDemo.Click

      Dim del As New MinSimpleDelegateType(AddressOf MinMetode)
      del(42, "MegetSimpelOgRetTåbeligDelegateDemo")
       
    End Sub

    Private Sub MinMetode(ByVal minParameter As integer, ByVal caption As String)
        System.Windows.Forms.MessageBox.Show("Svaret er " + minParameter.ToString(), caption)
    End Sub 
    private delegate void MinSimpleDelegateType(int minSimpleParameter, string text); 

    private void btnMegetSimpelOgRetTåbeligDelegateDemo_Click(object sender, System.EventArgs e) 
    { 
      MinSimpleDelegateType del = new MinSimpleDelegateType(MinMetode); 

      del(42, "MegetSimpelOgRetTåbeligDelegateDemo"); 
    } 

    private void MinMetode(int minParameter, string caption) 
    { 
      System.Windows.Forms.MessageBox.Show("Svaret er " + minParameter.ToString(), caption); 
    }

Når vi klikker på knappen får vi vist en messagebox med teksten "Svaret er 42". Ganske smart ikke sandt? Tjah... en og anden blandt læserne undrer sig muligvis nok over det smarte i ovenstående kode, når det inddirekte metode-kalde i stedet kunne være implementeret ved et ganske simpelt og traditionelt direkte metode-kald:

    MinMetode(42, "MegetSimpelOgRetTåbeligDelegateDemo")
    MinMetode(42, "MegetSimpelOgRetTåbeligDelegateDemo");

Nej selve eksemplet er ganske rigtigt ikke særligt smart, men det er teknikken til gengæld, idet den indebærer en langt større fleksibilitet end det simple og direkte metodekald. Til gengæld er delegate-koden naturligvis ikke helt så eksplicit og "sikker", som et ganske almindeligt metodekald til "MinMetode"-metoden, idet delegate-objektet netop kan instansieres med en vilkårlig metode, der opfylder den af delegate-typen specificerede metode-signatur.

Løse bindinger med delegates

Nu vil man nok næppe lave kode, der er eksakt som i det første eksempel – der er det trods alt en hel del smartere at kalde metoden direkte, men hvad bruger man så egentlig delegates til? Styrken ved delegates er, at de muliggør repræsentation af metoder som objekter, der som bekendt for eksempel kan sendes med som parametre til andre metoder; metoder der herefter kan kalde den i delegaten medsendte metode.

I det følgende eksempel på dette vil vi fra en procedure generere strenge, som bl.a. består af simple heltal, men hvor formatteringen af tallene skal kunne variere fra gang til gang. En simpel og direkte implementation kunne være som følger:

    Public Enum HeltalsFormatering
      Decimalt
      Hexadecimalt
    End Enum
Public Function UdskrivVærdierneMedDirekteKald(ByVal arr As Integer(), ByVal formatering As HeltalsFormatering) As String Dim s As String = "" For n As Integer = 0 To arr.GetLength(0) - 1 s += "Tal # " + (n + 1).ToString() + " er " Select Case formatering Case HeltalsFormatering.Decimalt s += arr(n).ToString() Case HeltalsFormatering.Hexadecimalt s += arr(n).ToString("X") End Select s += vbCrLf Next Return s End Function
    public enum HeltalsFormatering 
    { 
      Decimalt, 
      Hexadecimalt, 
    } 

    public string UdskrivVærdierneMedDirekteKald(int[] arr,  HeltalsFormatering formatering) 
    { 
      string s = ""; 
      for (int n = 0;  n < = arr.GetLength(0) - 1; n++) 
      { 
        s += "Tal # " + (n + 1).ToString() + " er "; 
        if (formatering == HeltalsFormatering.Decimalt) 
        { 
          s += arr[n].ToString(); 
        } 
        else if (formatering == HeltalsFormatering.Hexadecimalt) 
        { 
          s += arr[n].ToString("X"); 
        } 
        s += "\r\n"; 
      } 
      return s; 
    }

Kald af "UdskrivVærdierneMedDirekteKald"-metoden kan nu ske som følger:

    Dim arr() As Integer = {17, 42, 117}

    System.Windows.Forms.MessageBox.Show(UdskrivVærdierneMedDirekteKald(arr, _
        HeltalsFormatering.Decimalt), "Decimalt")
    System.Windows.Forms.MessageBox.Show(UdskrivVærdierneMedDirekteKald(arr, _
        HeltalsFormatering.Hexadecimalt), "HexaDecimalt")
    int[] arr = {17, 42, 117}; 

    System.Windows.Forms.MessageBox.Show(UdskrivVærdierneMedDirekteKald(arr,
        HeltalsFormatering.Decimalt), "Decimalt"); 
    System.Windows.Forms.MessageBox.Show(UdskrivVærdierneMedDirekteKald(arr,
        HeltalsFormatering.Hexadecimalt), "HexaDecimalt");

Denne løsning er i sandhed simpel, og kan såmænd også sagtens gå an i mange sammenhænge, men hvis man ønsker at implementere endnu en mulighed for at formatere tallene (f.eks. som romertal), betyder det, at man skal ændre dels i "UdskrivVærdierneMedDirekteKald"-funktionen dels i "HeltalsFormattering"-enumereringen. Er funktionen og/eller enumereringen placeret i en komponent betyder det så naturligvis også, at man skal rekompilere denne. Selv om OO-purister finder det forkert overhovedet at bruge enumereringer og (eksempelvis) samhørende Select-/switch-statements, finder jeg, at det trods alt med pragmatiske briller i mange scenarier kan være en acceptabel løsning. Man skal blot være sig bevidst om den manglende fleksibilitet, der er ved at lave en så stærk kobling mellem "UdskrivVærdierneMedDirekteKald"-funktionen og de enkelte formaterings-implementationer. Naturligvis kunne man have pakket formaterings-implementationerne ind i deres egne funktioner, hvorved man kunne have løsnet lidt på den stærke kobling, men det er i den sidste ende en implementationsmæssig detalje set fra "UdskrivVærdierneMedDirekteKald"-funktionens side af.

Hvis man ønsker at skabe en mindre stærk binding mellem "UdskrivVærdierneMedDirekteKald"-funktionen og de enkelte formatterings-implementationer kan man eksempelvis benytte delegates til at opnå dette. Til dette formål definerer jeg en delegate, der specificerer mulige formateringsfunktioners metodesignaturer som følger:

    Private Delegate Function HeltalsFormateringsFunktion( ByVal heltal As integer) As String
    private delegate string HeltalsFormateringsFunktion(int heltal); 

I stedet for at lade udskrivnings-funktionen modtage information om, hvilken formatering der skal ske som en enumereringsværdi, defineres funktionen nu til at modtage et delegate-objekt af typen "HeltalsFormateringsFunktion" som parameter:

    Private Sub UdskrivVærdierneMedDelegate(ByVal arr As Integer(), _
ByVal formaterer As HeltalsFormateringsFunktion) Dim s As String = "" For n As Integer = 0 To arr.GetLength(0) - 1 s += "Tal # " + (n + 1).ToString() + " er " s += formaterer(arr(n)) s += vbCrLf Next System.Windows.Forms.MessageBox.Show(s, UdskrivVærdierneMedDelegate") End Sub
    private void UdskrivVærdierneMedDelegate(int[] arr, HeltalsFormateringsFunktion formaterer) 
    { 
      string s = ""; 

      for (int n = 0; n <= arr.GetLength(0) - 1; n++) 
      { 
        s += "Tal # " + (n + 1).ToString() + " er "; 
        s += formaterer(arr[n]); 
        s += "\r\n"; 
      }

      System.Windows.Forms.MessageBox.Show(s, UdskrivVærdierneMedDelegate"); 
    }

Vi kan nu definere forskellige formaterings-funktioner opfyldende delegate-typens metode-signatur. De to følgende formaterings-funktioner svarer til den funktionalitet, vi implementerede i "UdskrivVærdierneMedDirekteKald"-funktionen.

    Private Function Decimalt(ByVal heltal As Integer) As String
      Return heltal.ToString()
    End Function

    Private Function Hexadecimalt(ByVal heltal As Integer) As String
      Return heltal.ToString("X")
    End Function
    private string Decimalt(int heltal) 
    { 
     return heltal.ToString(); 
    } 

    private string Hexadecimalt(int heltal) 
    { 
      return heltal.ToString("X"); 
    }

Kald til "UdskrivVærdierneMedDelegate"-funktionen kan nu ske med følgende kode:

    Dim arr() As integer = {17, 42, 117}
    Dim delDecimalTal As NewHeltalsFormateringsFunktion(AddressOf Decimalt)
    UdskrivVærdierneMedDelegate(arr, delDecimalTal)

    Dim delHexadecimal As New HeltalsFormateringsFunktion(AddressOf Hexadecimalt)
    UdskrivVærdierneMedDelegate(arr, delHexadecimal)
    int[] arr = {17, 42, 117};
    HeltalsFormateringsFunktion delDecimalTal = new HeltalsFormateringsFunktion(Decimalt);
    UdskrivVærdierneMedDelegate(arr, delDecimalTal);
    
    HeltalsFormateringsFunktion delHexadecimal =  
        new HeltalsFormateringsFunktion(Hexadecimalt); 
    UdskrivVærdierneMedDelegate(arr, delHexadecimal);

Disse kald kunne sagtens være lavet mere kompakte, men jeg har for tydelighedens skyld valgt at definere temporære delegate-variable.

Umiddelbart virker denne kode nok ikke specielt meget mere simpel end koden med de direkte kald – snarere tværtimod. Til gengæld kan vi nu implementere nye formatteringsfunktioner uden at skulle lave om i "UdskrivVærdierneMedDelegate"-funktionen. Dette er specielt kraftfuldt, hvis man ikke har eller eksempelvis af abstraktionsmæssige årsager ikke ønsker at have adgang til sourcekoden, og altså dermed ikke umiddelbart er i stand til at ændre implementationen af "UdskrivVærdierneMedDelegate"-funktionen.

Endnu løsere bindinger

En alternativ måde at skabe delegate objekter på er ved at bruge en af de statiske/shared CreateDelegate-metoder på System.Delegate-klassen.

Så instansieringen af et delegate-objekt kan også formuleres som:

    delDecimalTal = CType(System.Delegate.CreateDelegate(GetType(HeltalsFormateringsFunktion), _
        Me, "Decimalt"), HeltalsFormateringsFunktion)
    delDecimalTal = (HeltalsFormateringsFunktion) System.Delegate.CreateDelegate(
        typeof(HeltalsFormateringsFunktion), this, "Decimalt");

CreateDelegate-metoden skaber den samme store fleksibilitet, som kendes fra reflektions-orienteret kode, men en fleksibilitet der naturligvis kommer med den sædvanlige pris af at være meget lidt typesikker, både fordi metoden blot angives ved dens navn (som en simpel streng), og fordi CreateDelegate-metoden returnerer et objekt af typen System.Delegate, som skal typecastes eksplicit til vores konkrete delegate-klasse (i nærværende tilfælde "MinSimpleDelegateType").

Multicast delegates

De delegate-typer, vi selv definerer, er afledt af typen System.MulticastDelegate, som igen er afledt af typen System.Delegate. Et multicast delegate-objekt er et delegate-objekt, som vha. de shared/statiske Combine-funktioner på System.Delegate-typen kan sættes til at indeholde referencer til flere metoder på én gang.

    Public Shared Function Combine(ByVal a As System.Delegate, ByVal b As System.Delegate) _
        As System.Delegate
    Public Shared Function Combine(ByVal delegates() As System.Delegate)  As System.Delegate
    public static System.Delegate Combine(System.Delegate a, System.Delegate b);
    public static System.Delegate Combine(System.Delegate[] delegates);

Lad os betragte de to forskellige funktioner ("MinEneMetode" og "MinAndenMetode") :

    Private Sub MinEneMetode(ByVal minParameter As Integer, ByVal caption As String)
      System.Windows.Forms.MessageBox.Show("MinEneMetode siger: " + _
          Decimalt(minParameter), caption)
    End Sub
                  

    Private Sub MinAndenMetode(ByVal minAndenParameter As Integer, ByVal s As String)
      System.Windows.Forms.MessageBox.Show("MinAndenMetode siger: " + _
          Hexadecimalt(minAndenParameter), s)
    End Sub
    private void MinEneMetode(int minParameter, string caption) 
    { 
      System.Windows.Forms.MessageBox.Show("MinEneMetode siger: " +  
          Decimalt(minParameter), caption); 
    } 

    private void MinAndenMetode(int minAndenParameter, string s) 
    { 
      System.Windows.Forms.MessageBox.Show("MinAndenMetode siger: " + 
          Hexadecimalt(minAndenParameter), s); 
    }

Hvis vi ønsker at lade en delegate indeholde referencer til både "Decimalt"- og "Hexadecimalt"-funktionerne, skaber vi først et kombineret delegate-objekt vha. Combine-funktionerne. Klient-koden vil efter at have kombineret to delegate-objekter (kombinerede delegate-objekter vil vi herefter betegne som sub-delegates) kunne kalde begge de i delegate-objekterne refererede funktioner ved ét enkelt kald til det kombinerede delegate-objekt. Så hvis vi ønsker at kalde de to funktioner ved kald af et enkelt delegate-objekt, kan det gøres som følger (hvor vi igen benytter "MinSimpleDelegateType"-delegate-typen):

    Dim delegates As MinSimpleDelegateType
    Dim del1 As New MinSimpleDelegateType(AddressOf MinEneMetode)
    Dim del2 As New MinSimpleDelegateType(AddressOf MinAndenMetode)


    delegates = CType(System.Delegate.Combine(del1, del2), MinSimpleDelegateType)
    delegates(42, "Simpel Combine: MinEneMetode, MinAndenMetode")
    MinSimpleDelegateType delegates; 
    MinSimpleDelegateType del1 = new MinSimpleDelegateType(MinEneMetode); 
    MinSimpleDelegateType del2 = new MinSimpleDelegateType(MinAndenMetode); 

    delegates = ((MinSimpleDelegateType) System.Delegate.Combine(del1, del2)); 
    delegates(42, "Simpel Combine: MinEneMetode, MinAndenMetode"); 

Kaldet til den kombinerede delegate resulterer altså i, at vi får vist to forskellige messageboxe.

Hvis en metode refereres flere gange fra en multicast delegate, vil et kald til delegaten kalde den givne funktion flere gange. Så hvis man vil kalde "MinEneMetode"-metoden én gang og "MinAndenMetode"-metoden to gange, kan det altså ske ved at kombinere de ønskede delegate-objekter ét af gangen.

    delegates = CType(System.Delegate.Combine(del1, _
        System.Delegate.Combine(del2, del2)), MinSimpleDelegateType)
    delegates = ((MinSimpleDelegateType)  System.Delegate.Combine(del1, _ 
System.Delegate.Combine(del2, del2)));

eller alternativt ved at kombinere et array af delegate-objekter

    Dim arrDelegate As MinSimpleDelegateType() = {del1, del2, del2}

    delegates = CType(System.Delegate.Combine(arrDelegate), MinSimpleDelegateType)
    MinSimpleDelegateType[] arrDelegate = {del1, del2, del2}; 

    delegates = ((MinSimpleDelegateType) System.Delegate.Combine(arrDelegate)); 

Lige som man kan kombinere flere delegates, kan man også fjerne referencer til givne metoder igen. Så hvis man ønsker at fjerne den ene reference til "MinAndenMetode"-metoden i ovenstående eksempel, kan det gøres ved at benytte den shared/statiske Remove-funktion på System.Delegate-typen en enkelt gang:

    delegates = CType(System.Delegate.Remove(delegates, del2), MinSimpleDelegateType)
    delegates = ((MinSimpleDelegateType)System.Delegate.Remove(delegates, del2)); 

Ønsker man at fjerne begge referencer til "MinAndenMetode"-metoden, kan man enten kalde ovenstående Remove-funktion to gange eller alternativt kalde den shared/statiske RemoveAll-funktion, der fjerner alle referencer til en given metode

    delegates = CType(System.Delegate.RemoveAll(delegates, del2), MinSimpleDelegateType)
    delegates = ((MinSimpleDelegateType)System.Delegate.RemoveAll(delegates, del2)); 

Normalt vil man ikke benytte delegates, der returnerer værdier. Grunden til dette er, at man ved kald af et multicast delegate-objekt automatisk får kaldt videre til alle de sub-delegates som multicast delegaten refererer. Dette viderekald er implementeret ved et simpelt loop gennem alle sub-delegate-objekterne med viderekald til disse, hvilket bl.a. betyder, at man som resultat af kaldet af multicast delegaten kun får returneret værdien af den sidst kaldte sub-delegate. En anden konsekvens af at der foretages et simpelt gennemløb er, at kaster blot en enkelt af sub-delegate-objekterne en exception, eksekveres de resterende sub-delegates ikke. Det betyder derfor som udgangspunkt, at man ikke bør lade delegates returnere værdier, og at man skal være meget omhyggelig med exception-handling i de fra sub-delegate-objekterne refererede metoder.

Man har dog også mulighed for selv at styre begivenhedernes gang ved egenhændigt at gennemløbe de sub-delegate-objekter, som ligger til grund for en kombineret delegate. Det gøres ved at kalde GetInvocationList-funktionen på det kombinerede delegate-objekt:

    Dim delegates As MinSimpleDelegateType

    Dim del1 As New MinSimpleDelegateType(AddressOf MinEneMetode)
    Dim del3 As New MinSimpleDelegateType(AddressOf MintredjeOgFejlbehæftedeMetode)
    Dim del2 As New MinSimpleDelegateType(AddressOf MinAndenMetode)

    Dim arrDelegate As MinSimpleDelegateType() = {del1, del3, del2}

    delegates = CType(System.Delegate.Combine(arrDelegate), MinSimpleDelegateType)

    For Each del As MinSimpleDelegateType In delegates.GetInvocationList

      Try
        del(42, "GetInvocationList")

      Catch ex As System.Exception
        System.Windows.Forms.MessageBox.Show("Vi fangede fejlen i " + _
            "'MintredjeOgFejlbehæftedeMetode'-metoden, og vi fortsætter blot med det næste kald." + _
            "GetInvocationList", MessageBoxButtons.OK, MessageBoxIcon.Error)
      End Try
    Next
    MinSimpleDelegateType delegates; 

    MinSimpleDelegateType del1 = new MinSimpleDelegateType(MinEneMetode); 
    MinSimpleDelegateType del3 = new MinSimpleDelegateType(MintredjeOgFejlbehæftedeMetode); 
    MinSimpleDelegateType del2 = new MinSimpleDelegateType(MinAndenMetode); 

    MinSimpleDelegateType[] arrDelegate = {del1, del3, del2}; 

    delegates = ((MinSimpleDelegateType)System.Delegate.Combine(arrDelegate)); 

    foreach (MinSimpleDelegateType del in delegates.GetInvocationList()) 
    { 
      try 
      { 
        del(42, "GetInvocationList"); 
      } 
      catch (System.Exception ex) 
      { 
        System.Windows.Forms.MessageBox.Show("Vi fangede fejlen i " + _
            "'MintredjeOgFejlbehæftedeMetode'-metoden, og vi fortsætter blot med det næste kald.", _
            "GetInvocationList", MessageBoxButtons.OK, MessageBoxIcon.Error); 
      } 
    } 

Bemærk at for-each konstruktionen indebærer et implicit-typecast af de enkelte sub-delegate-objekter til "MinSimpleDelegateType".

Delegates benyttes ofte til implementation af traditionelle callback-scenarier, hvor man ønsker, at en kaldt komponent skal kunne kalde tilbage til klienten, på forskellige tidspunkter undervejs i eksekveringen. Et eksempel på dette kunne være, at efterhånden som komponenten eksekverer et omfattende job, skal klienten kunne opdatere sit user-interface med information om, hvor langt komponenten er nået. Denne opdatering af user interfacet kunne være i form af en progressbar, en numerisk tæller eller lign. Jeg har lavet et lille simpelt eksempel på dette (kaldet "ProgressCallback"), som sammen med de øvrige eksempler kan downloades fra vores website

Sproglig understøttelse af delegates.

I artiklen har jeg benyttet mig af .NET frameworkets klasser til manipulation af delegates, men C# har også indbygget sprog-understøttelse, som man, hvis man foretrækker det, kan benytte i stedet.

Sprogunderstøttelsen består i at + og – operatorerne er blevet overloadet således, at:

        del = del1 + del2
    svarer til
        del = (DelegateType) System.Delegate.Combine(del1, del2)

        del += del1
    svarer til
        del = (DelegateType) System.Delegate.Combine(del, del1)
        del = del1 - del2
    svarer til
        del = (DelegateType) System.Delegate.Remove(del1, del2)
        del -= del1
    svarer til
        del = (DelegateType) System.Delegate.Remove(del, del1)

Første gang man ser eksempelvis et kald af System.Delegate.Combine implementeret vha. + operatoren, kan man nok godt undre sig noget over, hvad det er der foregår, idet den implicitte betydning af + i forbindelse med delegates nok ikke er den mest intuitive brug, man kan forestille sig af + operatoren, men disse overloads ses faktisk ret ofte anvendt i C#-kode. Til gengæld har den overloadede brug af + og – operatorerne udover deres mere kompakte fremtræden også den fordel, at de er typestærke af natur. Skal man have adgang til den fulde funktionalitet ved delegates, er det dog langt det letteste at benytte metoderne på System.Delegate-klassen.

Delegates kontra traditionel polymorfi

Delegates er en simpel men kraftfuld mekanisme til implementation af dels indirekte kald, dels diverse egentlige callback-scenarier, men delegates er på ingen måde den eneste mekanisme, der kan anvendes til sådanne implementationer. Andre muligheder kunne være polymorfe implementationer baseret på enten fælles superklasser eller interfaces, og det der svarer til en delegate, ville så være en klasse/et interface med kun en enkelt funktion.

For nogen tid siden holdt jeg sammen med Microsoft et virksomheds-seminar, hvor en af deltagerne (en udvikler med Java-baggrund) udtrykte en vis skepsis overfor delegates (og jeg citerer): "Delegates er underlige og formodentlig overflødige". Han refererede netop til muligheden for i stedet at benytte interfaces til implementation af de samme funktionaliteter. Og jeg medgiver da også, at jeg i mange situationer foretrækker interfaces, idet jeg opfatter dem som mere typestærke end delegates. På den anden side er de også noget mere tunge i brug: Man skal definere et interface med en enkelt metode (svarer til at definere delegate-typen), definere en klasse og lade den implementere interfacet (svarer til at definere den løse funktion, som en delegate kan referere) samt instansiere klassen (svarer til at instansiere delegate-objektet).

Delegates er simplere at benytte (når man vel at mærke har fanget princippet ved dem ;^) Dels er de mere fleksible, idet de kan referere både shared/statiske metoder såvel som instans-metoder. Og i modsætning til interfaces skal man ikke have et helt så stort apparat i luften – de er derfor velegnede til situationer, hvor der blot skal kaldes tilbage på eksempelvis en lokal metode på en form. Ja faktisk behøver man i modsætning til, hvad der er tilfældet for f.eks. interface-implementationer slet ikke at have adgang til kildekoden for den metode, man refererer fra en delegate. Naturligvis kan kald til sådanne metoder også ske ved brug af interface-baseret polymorfisme, men så kan kaldet ikke ske direkte og skal i stedet pakkes ind.

Den fleksibilitet som vi har i forbindelse med delegates kunne naturligvis også opnås ved brug af refleksion, men så er vi virkelig overladt til ren revolver-kode – så får vi trods alt langt mere typesikkerhed ved brug af delegates.

Delegates – endnu et værktøj i kodebæltet

Jeg er enig med den ærede Java-udvikler i, at vi i mange scenarier bør foretrække polymorfisme baseret på for eksempel brug af interfaces frem for delegates, men selvom delegates ikke løser alverdens problemer, finder jeg bestemt, at det er endnu et glimrende .NET-værktøj, vi kan have siddende parat i vores kodebælte.

Jeg har i denne artikel kun behandlet nogle udvalgte (de simpleste) aspekter af delegates. Af andre oplagte emner relateret til delegates, som vi ikke har behandlet i denne artikel, kan nævnes brugen af asynkrone kald til delegates og sammenhængen mellem events og delegates – emner der er oplagte at tage fat på i en kommende artikel.

Med til artiklen hører også kodeeksempler (lavet i både VB og C#), som du kan downloade her.