Jeg sad lige og så episode #105 på dnrTV, hvor emnet var: Hvordan Generics kan gøre din kode mindre, hurtigere og lettere at læse. Man lærer altid noget, ved at se hvordan andre bruger den teknologi, man selv bruger dag ud og dag ind. I episoden bliver der gjort stor brug af Extension Method, og jeg var lige ved at slukke, da jeg synes, jeg havde set nok om den nye feature i kompileren i DNF 3.5. Da var det emnet kom ind på opdatering af UI fra andre tråde. Her var der jo en, for mig, helt ny måde at bruge featuren på.
Jeg har i mit nuværende job ansvaret for en applikation, der fungerer som en service for en lang række industrimaskiner (Robotter). Disse maskiner rejser events i applikationen, og vi opdaterer UI med data fra disse events. Derfor har vi hundrede vis af linjer af kode, der ikke gør andet, end at checke for om den eksekverende tråd er UI-tråden, og i negativt fald; invoker en delegat der kan opdaterer UI:
[code=csharp] void tcc_Progress(object sender, EventArgs e)
{
TimeConsumingCalculations tcc =
sender as TimeConsumingCalculations;
// Check om det er en anden tråd der eksekverer
if (this.InvokeRequired)
{
// Invoke på UI tråden med delegaten
this.BeginInvoke(
new UpdateTextBoxDelegate(UpdateTextBox),
textBox1,
tcc.CurrentValue);
}
else
{
// Vi er i i UI tråden
UpdateTextBox(textBox1, tcc.CurrentValue);
}
}
// Nødvendig erklæring af delegat, for at kunne kalde Invoke
delegate void UpdateTextBoxDelegate(TextBox tb, string value);
// Metode der udfører opdateringen
void UpdateTextBox(TextBox tb, string value)
{
tb.Text = calculator.CurrentValue;
} [/code]
Jeg synes selv at denne form for kode er triviel, (og det er en af de eneste ting jeg misunder VB.NET for). Der er alle mulige ting der kan gå galt, og koden er umulig at læse og forstå – alt dette for at opdaterer et UI…
Nu blev jeg gjort opmærksom på en pænere måde at udføre dette check på, af udviklerne på Advosol – de har en lidt mere kompakt måde at klare problemet på:
[code=csharp] void tcc_Progress(object sender, EventArgs e)
{
TimeConsumingCalculations tcc =
sender as TimeConsumingCalculations;
if (this.InvokeRequired)
{
this.BeginInvoke(
new EventHandler(tcc_Progress),
sender, e);
return;
}
textBox1.Text = tcc.CurrentValue;
} [/code]
Her udnytter man, at metoden altid har en delegat-erklæring, ellers kunne den jo ikke være eventhandler, og kalder Invoke rekursivt på metoden. Det fjernede behovet for en lang række delegat-erklæringer og opdateringsmetoder. Koden blev mindre og lettere at læse.
Nu fik jeg så øjnene op for Extension Method, og kan nu gøre koden endnu mere strømlinet. Jeg tager blot ovenstående trick og putter det i en Extension på selve Form objektet:
[code=cshrap] static class FormExtensions
{
public static void UpdateTextBox(
this Form form,
TextBox textBox,
string value)
{
if (form.InvokeRequired)
{
form.BeginInvoke(
new Action
(form.UpdateTextBox),
textBox,
value);
}
else
{
textBox.Text = value;
}
}
} [/code]
..og kalder nu min nye metode på formen:
[code=csharp] void tcc_Progress(object sender, EventArgs e)
{
TimeConsumingCalculations tcc =
sender as TimeConsumingCalculations;
this.UpdateTextBox(textBox1, tcc.CurrentValue);
} [/code]
Med et slag fik vi reduceret delegat metoden til en linje. Denne ene feature, har reduceret vores kode med over 2000 linjer. Jeg ved godt at CLR koden ikke har forandret sig, og at applikationen ikke er blevet mere effektiv, men koden er da blevet meget lettere at læse og vedligeholde. Det var en god refactoring.
Hvad er så ulempen ved Extension Method featuren? Man kan jo forledes til at kode løs, og lave metoder til alt. Ja faktisk, kan man typisk fjerne alle sine hjælpe klasser med denne feature, og jeg har da også selv gjort det nogle gange – f.eks. min ToJson extension. I blogssfæren bliver der også gjort forsøg med featuren – se f.eks. Deldy og Daniel. Problemet er det faktum, at man ikke har styr på den kode man laver tilføjelser til. Her har jeg lavet en tilføjelse til System.Windows.Forms.Form objektet, og jeg synes selv at min koder er blevet 100 gange pænere, men jeg har måske skabt mig selv problemer i fremtiden. Hvad nu hvis DNF 4.0 har udvidet Form med en metode der kaldes UpdateTextBox? Og hvad nu hvis den metode har samme form som min extension, men bare udfører noget helt andet funktionalitet? Så har jeg et stort problem med min kode. Den kan i værste fald være fuldstændig ubrugelig. Så uanset hvor fed den feature er, så skal man bruge den med omtanke, og sørge for at begrænse brugen. Min ToJson metode, er en udvidelse til Object – det vil sige at jeg får problemer hvis bare én af klasserne i DNF implementerer en metode med det navn.
Carry on implementing…