delegates
events
predicate
Børre Stenseth
C#>Delegates

Delegates

Hva
Delegates

Som navnet tilsier er delegates en mekanisme for å delegere oppgaver. Det vil si at vi ønsker å delegere, eller tilordne, en metode til et objekt eller en metode. I C# kan vi ikke sende med en metode direkte som parameter, vi må "pakke den inn" i en delegate. I denne modulen skal vi se på to eksempler. Eksempelet sortering er en demonstrasjon av hvordan vi kan plante en sammenligningsfunksjon i en sorterinhgsalgoritme. Deretter skal vi se på bruk av delegates i en typisk sitiasjon: planting av metoder for event-handtering.

Sortering

Merk at dette ikke er noen oppskrift på effektiv sortering. Vitsen er bare å finne et enkelt eksempel der bruken av delegates er tydelig.

demo

Koden i Form1 er slik:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace delegate1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        
        // a delegate
        public delegate bool Compare(string e1, string e2);
        // sorting by bubbling
        // Parameter:
        // a list
        // a delegate with a method for comparing elements
        public static void bubble(String[] wlist, Compare cmp)
        {
            bool isOk;
            do
            {
                isOk = true; 
                for (int ix = 0; ix < wlist.Length - 1; ix++)
                    if(cmp(wlist[ix],wlist[ix+1]))
                    {
                        string tmp = wlist[ix];
                        wlist[ix] = wlist[ix + 1];
                        wlist[ix + 1] = tmp;
                        isOk = false;
                    }
            } while (!isOk);
        }
        
        // the two functions we will send with the delegate
        private bool SortAscending(string s1, string s2)
        {return s1.CompareTo(s2) > 0;}
        
        private bool SortDescending(string s1, string s2)
        {return s1.CompareTo(s2) < 0;}
        
        // show result
        private void Display(String[] wlist)
        {
            listBox1.Items.Clear();
            listBox1.Items.AddRange(wlist);
        }
        
        // intiating the wordlist
        private void Form1_Load(object sender, EventArgs e)
        {
            textBox1.Text = @"en demonstrasjon av delegates";
            Display(textBox1.Text.Trim().Split(' '));
        }
        // when we want alphabetic order
        private void buttonAscending_Click(object sender, 
            EventArgs e)
        {
            String[] wlist = textBox1.Text.Trim().Split(' ');
            bubble(wlist, new Compare(SortAscending));
            Display(wlist);
        }
        // when we want reverse alphabetic order
        private void buttonDescending_Click(object sender, 
            EventArgs e)
        {
            String[] wlist = textBox1.Text.Trim().Split(' ');
            bubble(wlist, new Compare(SortDescending));
            Display(wlist);
        }
    }
}

Eventhandlere

Det å fange opp begivenheter, events, er en typisk problemstilling i f.eks. all GUI-programmering, og forskjellige språk og forskjellige plattformer har opp gjennom årene hatt forskjellige løsninger på dette. Problemstillingen er i korthet slik:

Vårt program skal respondere på en begivenhet som vi ikke vet når intreffer, og vi ønsker å spesifisere en metode som kan handtere begivenheten. La oss ta et museklikk som eksempel. Museklikket registreres av operativsystemet og legges i en kø av begivenheter som skal behandles. Når vårt museklikk kommer først i denne køen, rutes det til det programmet som har fokus, vårt program. Så er det opp til vårt program hvor ansvaret for å respondere på begiveheten skal plasseres, og hva som skal gjøres.

En prinsippskisse for Windows kan se slik ut:

messages1

Metoden WinMain ligger i bakgrunnen og tar imot alle meldinger som er rutet til vårt program. Så fordeles disse til de respektive vinduene, GUI-komponentene. I de tidligste versjoene av Windows var denne mekanismen eksplisitt og som programmerer måtte du lage generelle metoder for å handtere begivenheter. Disse metodene måtte "eksporteres" som entrypunkter til operativsystemet, altså som adresser til behandlingsfunksjoner. Innmaten i disse behandlingsmetodene besto stort sett av en switch med et case for de begivenhetene vi var interesserte i, av og til omtalt som: "The Long Switch". Hvis du vil ha en liten smak av hvordan det kunne se ut kan du se på kildekoden til et enkelt c-program som tegner ut røde prikker i et vindu: cdots.c og cdots.h

I dagens systemer trenger vi ikke ta et slikt helhetsansvar. Vi kan nøye oss med å angi metoder for hver begivenhet, for hver GUI-komponent. Og bare de begivenhetene vi ønsker å respondere på. De begivenetene vi ikke er intteresserte i blir behandlet på en standard måte. Hva som er standard eller default avhenger av typen GUI-komponent. F.eks. trenger vi vanligvis ikke skrive noen metode for å flytte fokus til en komponent vi klikker på. Vi trenger heller ikke skrive noen metode for at tegn vi skriver i et inputfelt skal legges til teksten.

Vi kan se på et enkelt eksempel. Når vi designer et GUI i Visual Stdio og tilordner en behandlingsfunksjon for en begivenhet, generer GUI-editoren en kodebit som kan se slik ut, for et ComboBox-object, myBox:

this.myBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.myBox.FormattingEnabled = true;
this.myBox.Location = new System.Drawing.Point(11, 150);
this.myBox.Name = "comboBoxView";
this.myBox.Size = new System.Drawing.Size(174, 21);
this.myBox.TabIndex = 3;
this.myBox.SelectedIndexChanged += 
     new System.EventHandler(this.myBox_SelectedIndexChanged);

Det er den siste linja som er interessant for vårt resonnement. SelectedIndexChanged er et EventHandler-objekt. new System.EventHandler skaper en ny delegate som blir lagt til de som eventuelt måtte være i SelectedIndexChanged fra før. Parameteren til konstrukøren er en funksjon som vi må skrive: myBox_SelectedIndexChanged.

I Java er begrepet Listener sentralt i å implementere tilsverende mekanismer. Javacode som tilordner en metode til en begivenhet kan se slik ut:

myBox.addItemListener(new java.awt.event.ItemListener() {
    public void itemStateChanged(java.awt.event.ItemEvent evt) {
        myBoxItemStateChanged(evt);
    }
});

Her har vi brukt teknikken med å tilordne en anonym ItemListener. Koden vi må skrive er : myBoxItemStateChanged.

"gjør det selv"

I stedet for å overlate til GUI-editoren å lage kode for oss kan vi gjøre det selv. Ulempene er selvsagt at vi må håndkode alle properties i komponentene, alle posisjoner etc. Men muligheten for en annen programarkitektur enn den GUI-designeren inviterer til er tilstede. Et enkelt eksempel. Koden nedenfor er all koden i en consoleapplikasjon som skaper og vedlikeholder sin egen form:

own
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace ownWindow
{
    public class myForm : Form
    {
        private static bool done = false;
        public myForm()
        {
            // delegating
            this.Closing += new CancelEventHandler(this.Form_Closing);
            this.Resize += new EventHandler(this.Form_Resize);
            this.Paint += new PaintEventHandler(this.Form1_Paint);
        }
        private void Form_Closing(object sender, CancelEventArgs e)
        {
            done = true;
        }
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.DrawString(String.Format(@"{0} X {1} pixels",
                                  this.Width, this.Height),
                                  new Font(this.Font, FontStyle.Bold),
                                  new SolidBrush(Color.Black),
                                  new Point(20, 20));
        }
        private void Form_Resize(object sender, EventArgs e)
        {
            this.Invalidate();
        }
    }
    class Program
    {
        private static bool done = false;
        static void Main(string[] args)
        {
            myForm form = new myForm();
            form.SetBounds(100, 100, 300, 300);
            form.FormBorderStyle = FormBorderStyle.Sizable;
            form.Text = "Show size";
            // show the form
            form.Show();
            // main loop
            while (!done)
            {
                Application.DoEvents();
            }
            // take form down
            if (form != null)
            {
                form.Hide();
                form.Close();
                form = null;
                Application.Exit();
            }
        }
    }
}

Predicates

Her brukt som søkefilter.

Vi bygger en enkel applikasjon med en liste av personer. Dataene er basert på en textressurs: dataPredicate.txt, og personklassen er beskrevet slik:

_person

Vi lager en Form-application med to lister og en button med følgende kode:

predicatescreen
_Form
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace predicate
{
    public partial class Form1 : Form
    {
        List<person> pList;
            
        public Form1()
        {
            InitializeComponent();
            loadData();
        }
        private void loadData()
        {
            pList=person.makeList(Properties.Resources.data);
            listBox1.Items.Clear();
            foreach(person p in pList)
                listBox1.Items.Add(p);
        }
        // fill list2 with haldensere
        private void button1_Click(object sender, EventArgs e)
        {
            listBox2.Items.Clear();
           
            // Alternative 0:
            //foreach (person p in pList)
            //    if (p.Adresse.CompareTo("Halden") == 0)
            //        listBox2.Items.Add(p);
             
            //Alternative 1
            ////foreach (person p in pList)
            ////    if (haldenser(p))
            ////        listBox2.Items.Add(p);
             
            //Alternative 2:
            foreach (person p in pList.FindAll(haldenser))
                listBox2.Items.Add(p);
             
            //Alternativ 3:
            //listBox2.Items.AddRange(pList.FindAll(haldenser).ToArray());
        }
        private static bool haldenser(person p)
        {
            return p.Adresse.CompareTo("Halden")==0;
        }
    }
}

Den interessante koden er kallet på pList.FindAll() med en metode, haldenser, som parameter. Vi må lese dette slik at vi ber metoden FindAll å bruke en delegate til å filtrere hva som skal brukes. Det finnes flere varianter av dette. Sjekk hva List-klassen har for slags funksjonalitet.

En variant

Vi bruker de samme dataene og beholder klassen person. Vi gjør to endringer. For det førtse putter vi flere typer objekter inn i lista, for det andre lager vi liste2 basert på liste1.

_Form
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace predicatetull
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            loadData();
        }
        
        private void loadData()
        {
            listBox1.Items.AddRange(person.makeList(Properties.Resources.data).ToArray());
            listBox1.Items.Add("anytstring");
            listBox1.Items.Add("whatever");
        }
        private void button1_Click(object sender, EventArgs e)
        {
            listBox2.Items.Clear();
            List<object> ol=listBox1.Items.Cast<object>().ToList<object>();
            listBox2.Items.AddRange(ol.FindAll(haldenser).ToArray());
        }
        private bool haldenser(object p)
        {
            if (p.GetType().Equals(typeof(person)))
                return ((person)p).Adresse.CompareTo("Halden") == 0;
            else
                return false;
        }
    }
}
Referanser
  • Sorteringsprogrammet:
    https://svn.hiof.no/svn/psource/Csharpspikes/delegate1
  • Det hjemmesnekrede programmet:
    https://svn.hiof.no/svn/psource/Csharpspikes/language/ownWindow
  • Det "gamle" c-prosjektet som tegner prikker:
    https://svn.hiof.no/svn/psource/Csharpspikes/old-c-case
  • Predicates:
    https://svn.hiof.no/svn/psource/Csharpspikes/predicate
  • Predicates, variant:
    https://svn.hiof.no/svn/psource/Csharpspikes/predicate2
Vedlikehold

B.Stenseth, revidert januar 2011

(Velkommen) C#>Delegates (Assemblies)