Jeg har for lang tid siden skrevet et indlæg på dotninjas om, hvordan man laver en single file generator (også kaldet et custom tool) til Visual Studio.  Desværre ligger det så lang tid tilbage, at jeg ikke kan grave et link frem.  En single file generator benyttes mange steder til at genere kode ud fra indholdet af en anden fil.  For eksempel benyttes det af Entity Framework til at lave klasser ud fra en XML fil som vist på billedet herunder, hvor Custom Tool er sat til “EntityModelCodeGenerator”.

CustomTool

For at lave en Single File Generator skal man implementere nogle bestemte COM interfaces og registrere sin dll på en bestemt måde i registry, og metoden fra min gamle (bortkomne) artikel har fungeret fint indtil fornylig, hvor jeg langt om længe fik mig en Windows 7 64 bit boks på arbejdet.  Jeg har prøvet alt på den maskine både med Wow32To64, og hvad ved jeg.  Men VS 2010 blev ved med fuldstændig at ignorere mit gamle custom tool.

På MSDN er der dokumentation af, hvordan man laver Single File Generators, og der findes også et sample i VS SDK’en, som beskriver, hvordan man kan lave en Single File Generator vha. VS Extensions.  Eksemplet virker fint, men det er ikke særlig godt beskrevet.  Især er det ikke beskrevet, hvordan man sætter projektet op til at blive kompileret helt korrekt, så registry entries bliver genereret korrekt.  Det håber jeg, at dette indlæg kan råde bod på.

Målet er at lave en Single File Generator, der nemt kan distribueres vha. af en .vsix pakke, så den nemt kan installeres som en Visual Studio Extension.  Dette er noget nemmere end min gamle metode, hvor der skulle registreres en dll vha. regasm.exe.

Det er en forudsætning, at man har Visual Studio Service Pack 1 samt Visual Studio SDK Service Pack 1 installeret.  Når SDK’en er installeret, er der adgang til et par VS projekttemplates, som kan bruges til at lave VS Extensions.  Til at lave en Single File Generator, skal man bruge den template, der hedder “VSIX Project”.

vsixproject

Efter projektet er oprettet, skal man først tilføje nogle referencer til en række dll’er for at få fat i de rigtige COM interfaces.  Det drejer sig om følgende:

  • EnvDTE
  • EnvDTE80
  • Microsoft.VisualStudio.Designer.Interfaces
  • Microsoft.VisualStudio.OLE.Interop
  • Microsoft.VisualStudio.Shell.10.0
  • Microsoft.VisualStudio.Immutable.10.0
  • Microsoft.VisualStudio.Interop
  • Microsoft.VisualStudio.Interop.8.0
  • Microsoft.VisualStudio.Interop.9.0
  • Microsoft.VisualStudio.Interop.10.0
  • VSLangProj
  • VSLangProj2
  • VSLangProj80

Det er muligt, at nogle enkelte referencer ikke er nødvendige.  Det har jeg ikke testet.  Det er i øvrigt vigtigt, at alle referencer har “Embed Interop Types” sat til “False”.

EmbedInteropType

Med det ovennævnte sample følger nogle standardimplementationer af to interfaces IVsSingleFileGenerator og IObjectWithSite.  Det nemmeste er at bruge disse implementationer, så vi kopierer BaseCodeGenerator.cs og BaseCodeGeneratorWithSite.cs.  Bemærk, at BaseCodeGenerator indeholder nogle Trace.WriteLine statements, man skal fjerne for at kompilere, hvis man kopierer de to klasser direkte fra eksempelkoden.

Dernæst tilføjes filen CodeGeneratorRegistrationAttribute.cs, som kommer med SDK’en, og som standard ligger under “C:\Program Files (x86)\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Source\CSharp\RegistrationAttributes\”.  Denne klasse indeholder en implementation af en custom attribute, som VS benytter til at generere de nødvendige registry entries i en såkaldt .pkgdef fil.  Mere om det om lidt.

Det er nu tiden til at implementere selve kodegenereringen.  Dette gøres ved at tilføje en klasse, som skal nedarve fra BaseCodeGeneratorWithSite.

    [ComVisible(true)]
    [Guid("A4008ECD-02C6-418F-A0BD-083461479064")]
    [CodeGeneratorRegistration(typeof(DotNinjasCodeGenerator), 
        "DotNinjas sample C# code generator", 
        vsContextGuids.vsContextGuidVCSProject, 
        GeneratesDesignTimeSource = true)]
    [ProvideObject(typeof(DotNinjasCodeGenerator))]
    public class DotNinjasCodeGenerator : BaseCodeGeneratorWithSite
    {
        protected override byte[] GenerateCode(string inputFileContent)
        {
            return Encoding.ASCII.GetBytes("// Some generated code goes here");
        }
    }

Ikke overraskende er det metoden GenerateCode, der kaldes af VS, når man kører sit custom tool.  Parameteren inputFileContent vil indeholde indholdet af den fil, som vores custom tool bliver anvendt på.

Bemærk den imponerende række attributes på klassen.

Klassen skal nødvendigvis være COM visible og dermed også have en Guid attribute (husk også at gøre hele assembly COM visible).  CodeGeneratorRegistration benyttes som nævnt til at generere de nødvendige registry settings automatisk.  Disse registry settings ryger i en .pkgdef fil.  Bemærk vsContextGuids.vsContextGuidVCSProject som angiver, at vores kodegenerator vil generere C# kode.  ProvideObject fortæller VS, hvilken klasse der skal oprettes til at generere koden.  Det er et krav, at klassen markeret med ProvideObject skal have en default constructor.  Bemærk at det lidt overraskende er ProvideObjectAttribute, der bestemmer navnet på vores generator.  Dvs. i dette eksempel er det “DotNinjasCodeGenerator”, der skal benyttes som navnet på vores custom tool i VS.

Projektet er nu klar til at kompilere, og der kommer ganske rigtigt en .vsix fil ud som resultat.  Desværre er det ikke nok.  Der mangler en dll og der mangler en .pkgdef fil med de nødvendige registry entries.  For mig var det lidt et mysterium, hvordan jeg fik disse filer genereret, men det kræver, at man redigerer direkte i .csproj filen.  Så start din favorit csproj editor (såsom notepad) og slet følgende linjer:

    <GeneratePkgDefFile>false</GeneratePkgDefFile>
    <IncludeAssemblyInVSIXContainer>false</IncludeAssemblyInVSIXContainer>
    <IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
    <IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
    <CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
    <CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>

GeneratePkgDefFile skal væk, så vi får vores .pkgdef fil, og CopyBuiltOutputToOutputDirectory forhindrer, at der bliver kompileret en dll.  Tilføj desuden følgende PropertyGroup i .csproj filen og gem den:

  <PropertyGroup>
    <RegisterOutputPackage>true</RegisterOutputPackage>
    <RegisterWithCodebase>true</RegisterWithCodebase>
  </PropertyGroup>

Efter reload af den nye projektfil, vil en kompilering give følgende output:

projectoutput

Man kan nu teste pakken ved at trykke F5 for debug, hvilket vil starte en ny instans af VS, hvor man kan afprøve vores custom tool.  Vi vil i stedet prøve vores vores custom tool i den virkelige verden.  Så start med at lukke alle instanser af VS og dobbeltklik på .vsix filen og tryk “Install”.  Dette vil installere vores custom tool som en VS Extension, og ved opspart af VS vil man ganske rigtigt kunne finde DotNinjasSingleFileGenerator som installeret extension.  Ønsker man at angive en lidt mere sigende beskrivelse end bare “Empty VSIX project”, kan det gøre ved at rette i source.extension.vsixmanifest filen i VS.

extension

For at teste DotNinjasSingleFileGenerator, opret et nyt C# projekt, tilføj en fil af en type, som normalt ikke benyttes i et C# projekt (f.eks. en .txt fil men ikke en .cs fil) og angiv DotNinjasCodeGenerator som custom tool, da det var DotNinjasCodeGenerator, der blev angivet som type til ProviderObjectAttribute.

customtoolinaction

Højreklik på din fil og vælg “Run custom tool”, hvilket vil resultere i en .cs fil indholdende den genererede kode.

Det er et meget positivt tiltag fra Apple, at de understøtter Nikon’s RAW format (dvs. .nef filer).  Det eneste, der er påkrævet, er Apples Camera Connection Kit til sølle 200 kr., og så er man kørende.  Sæt camera connector i iPad’en og forbind med USB kabel til kameraet, så importeres billederne til iPad’en.

Man skal dog lige være opmærksom på en lille detalje.  Billederne ligger i iPad’en som .nef filer, medmindre man i vanvare kommer til at trykke rotér-knappen (se skærmbillede).  Uden yderligere advarsel bliver billedet konverteret til .jpg under roteringen.  Det skal man lige være opmærksom på, hvis man har tænkt sig senere at flytte RAW billederne til PC til videre efterbehandling.

foto

Benytter man HTTP stacken fra Silverlight – herunder kald af WCF services over HTTP – sker kaldet asynkront.  Derfor skal man passe på, hvis man opdaterer brugerkontroller i sin callback funktion, da den kan blive kaldt på en anden tråd end den, der ejer brugerkontrollen.  Det er gamle nyheder.  Bruger man et pattern som f.eks. MVVM, hvor opdatering af ens view sker gennem binding til properties på en viewmodel, sker marshalling til UI tråden automatisk af Silverlight’s binding mekanisme.  I andre tilfælde er man nødt til selv at sørge for, at brugerinterfacet opdateres på den rigtige tråd f.eks. gennem Dispatcher.

Jeg har først fornylig opdaget, at callbacks fra WCF services i nogle tilfælde automatisk bliver flyttet til den kaldende tråd i Silverlight.  Det gælder også Silverlight til WP7.  Det sker i det tilfælde, hvor man benytter client proxy klasserne, der genereres, når man vælger “Add service reference” fra Visual Studio. 

Lad os sige, at jeg har en service kaldet Service1 med en metode DoWork.  Jeg har tilføjet en reference til servicen gennem VS, hvorved et namespace ServiceReference1 med en proxy klasse kaldet Service1Client er blevet oprettet.  Følgende kode er dermed fuldt lovlig:

label1.Text = Thread.CurrentThread.ManagedThreadId.ToString();

ServiceReference1.Service1Client service = new ServiceReference1.Service1Client();
service.DoWorkCompleted += (s, args) =>
    {
        // Dette kald er fuldt lovligt, og label1 og label2 vil vise samme thread id.
        label2.Text = Thread.CurrentThread.ManagedThreadId.ToString();
    };
service.DoWorkAsync();

Kigger man dybt nok i de genererede klasser, kan man se, at de klassen AsyncOperation benyttes til at flytte kaldet tilbage til den kaldende tråd.  Har man brug for at lave noget arbejde i sin callback fra sin service, må man undlade at bruge den genererede client proxy og f.eks. bruge ChannelFactory eller noget andet.  Forrige eksempel er funktionelt ækvivalent med nedenstående, hvor marshalling til UI tråden sker manuelt gennem brug af AsyncOperation.  Bemærk at AsyncOperation skal oprettes gennem AsyncOperationManager.CreateOperation:

AsyncOperation asyncOp;

void CallWcf()
{
    asyncOp = AsyncOperationManager.CreateOperation(1);
    ServiceReference1.IService1Channel channel 
        = new ChannelFactory<ServiceReference1.IService1Channel>("BasicHttpBinding_IService1")
        .CreateChannel();
    channel.BeginDoWork(DoWorkCallback, channel);
}

void DoWorkCallback(IAsyncResult result)
{
    ServiceReference1.IService1Channel channel = result.AsyncState as ServiceReference1.IService1Channel;
    channel.EndDoWork(result);

    asyncOp.PostOperationCompleted(new SendOrPostCallback(o =>
        {
            label2.Text = (string) o;
        }), Thread.CurrentThread.ManagedThreadId.ToString());
}

I stedet for AsyncOperation kunne man også vælge at bruge SynchronizationContext eller Dispatcher direkte.  Faktisk er AsyncOperation blot en wrapper omkring SynchronizationContext, og i Silverlight er SynchronizationContext en instans af DispatcherSynchronizationContext, som igen er en wrapper omkring Dispatcher.  Så Dispatcher er den rigtige helt her.

Som jeg har nævnt før, og som alle nok efterhånden har hørt, så kan WP7 ikke synkroniseres med Outlook.  Det kom som lidt af en overraskelse for mig.  Jeg havde helt klart en forventning om, at Zune ville kunne synkronisere Outlook.  Efter det første chok havde lagt sig, begyndte jeg naturligvis at søge efter andre metoder.

WP7 synkroniserer helt perfekt med OWA, Windows Live og GMail.  Min første tanke var derfor at bruge firmaets OWA.  Desværre arbejder jeg i et firma, hvor OWA er et fy-ord, og man skal gennem VPN, Citrix og alt muligt, for at komme i nærheden af sin mailbox.

Næste bud på listen var Windows Live.  Mærkeligt nok var jeg ikke i stand til finde en mulighed for at synkronisere Outlook til Windows Live.  Der er mulighed for at få sin Windows Live kalender vist i Outlook, men det er den forkerte vej.  Hvis nogen kender en måde at synkronisere mellem Outlook og Windows Live, så lad mig endelig høre.

Sidste mulighed var GMail.  Google har deres egen Google Calendar Sync.  Desværre kunne jeg ikke få den til at virke overhovedet.  Den kom med en underlig fejl, og jeg har Google mistænkt for at have skrinlagt komponenten.  Ærgerligt.  Efter noget søgen faldt jeg over Ogg Sync.  Ogg Sync kan ifølge eget udsagn synkronisere både kalender og kontakter, hvilket var, hvad jeg havde brug for.  Jeg købte Ogg Sync (for $30 tror jeg), og synkronisering af Outlook kalender med GMail fungerede perfekt.  Men desværre kan man ikke bruge samme licens til synkronisering af kalender og kontakter.  Om det er en bug i Ogg Sync vides ikke, for support svarer ikke på mails, og der er intet nævnt på web sitet.  Så pas på med Ogg Sync!

Efter lidt mere søgning fandt jeg GO Contact Sync Mod, som kan synkronisere Outlook kontakter med GMail.  Det fungerer næsten perfekt.  Der er dog nogle problemer med at synkronisere kontakter, som bare har et firmanavn.  Men det er småting.

Efter møge og besvær, begyndte Google Calendar Sync på magisk vis også at virke, så Ogg Sync er sendt på pension, og jeg er endt med følgende setup:

  • Google Calendar Sync til at synkronisere kalender med GMail.
  • GO Contact Sync Mod til at synkronisere kontakter med GMail.

Det er lidt noget rod at skulle have flere “services” kørende bare for at kunne synkronisere.  Men der er også fordele ved, at Zune ikke kan synkronisere på desktoppen.  Sålænge min computer bare er tændt på arbejdet, vil Exchange på arbejdet være opdateret med de aftaler, jeg ligger ind i min WP7 – og omvendt.  Det er faktisk genialt, og nu hvor det bare spiller, synes jeg klart det er en bedre løsning en synkronisering med Outlook. 

Samtidig er min WP7 synkroniseret med en OWA fra mit eget firma.  I WP7’s kalender bliver aftaler fra de forskellige kalendere vist med forskellige farver, så man har fuldstændig styr på det.

Er der andre, som har erfaringer med synkroniseringen, og som måske har en smartere løsning, så vil jeg gerne høre det.

Siden april 2010 har jeg overladt dotninjas.dk fuldstændig i ne0san’s og pandasan’s kompetente hænder.  Men jeg har fornylig anskaffet mig en LG Optimus 7, der kører med Windows Phone 7, og sådan et indkøb er et oplagt emne til en blog, for der skal lidt arbejde til for at få det til at køre optimalt.  Mere om det nedenfor og forhåbentlig i nogle følgende indlæg.

Jeg vil egentlig ikke bruge så meget tid på at anmelde selve telefonen.  Den er fin og lækker i designet (men nok ikke “iPhone 4 fin og lækker”) med en dejlig stor skærm, og selve WP7 OS er et frisk pust ovenpå iOS og alle iOS klonerne, der er derude.  Den kan ikke multitaske og andet sjov, men det fungerer rigtig godt med tiles og de såkaldte panorama views, og når apps følger WP7’s design guide kaldet Metro er tingene meget lækre at arbejde med.

Dette var min anmeldelse af WP7 og LG Optimus 7.  Nu videre til opsætning, så man kan få lov til at bruge sine surt optjente penge i marketplace.

Et af de største problemer med WP7 i Danmark er, at den ikke officielt er kommet til Danmark.  Det betyder, at man som dansker ikke umiddelbart har adgang til marketplace, hvilket er Microsoft’s svar på iTunes App Store.  Men fortvivl ej – der er (vistnok flere) måder at omgå det på.  Følgende er, hvad jeg gjorde for at få adgang til marketplace i UK:

  1. Opret en Windows Live konto, hvor du angiver UK som dit hjemland.
  2. Opret en en Zune konto, hvor du angiver en rigtig adresse i UK.  Jeg brugte min storesøster’s adresse, idet hun tilfældigvis bor i England.  Denne adresse benyttes som billing address, men jeg har ikke oplevet, at der rent faktisk bliver sendt noget fysisk brev til adressen fra Zune.
  3. Tilknyt et kreditkort til den nyoprettede Zune konto.
  4. Download og installer Zune softwaren.  Zune benyttes til at synkronisere musik, video og billeder med telefonen.  Zune synkroniserer ikke med Outlook, og der er ingen umiddelbar måde at gøre det på.  Det vil jeg måske skrive om på et andet tidspunkt. 
  5. Zune klienten benytter de regionale settings på computeren, og så længe de er sat til Danmark, vil Zune ikke tillade login og adgang til marketplace via din pc.  Gå derfor i kontrolpanelet og vælg “Skift grænsefladesprog”.  Gå til fanen “Placering” og vælg “Storbritannien”.    Udklip
  6. Start Zune op igen og du vil opdage en “Sign in” knap øverst til højre.

Så er det tid til at sætte selve telefonen op.  Jeg kan ikke helt huske fremgangsmåden her, men jeg mener, det var noget i retning af følgende:

  1. Vælg “Settings”.
  2. Vælg “email & accounts”. 
  3. Vælg “add an account” og klik “Windows Live”.  Jeg mener at kunne huske, at WP7 kommer med en advarsel om, at den første oprettede Windows Live konto vil være den, der fremover er tilknyttet telefonen.  Derfor skal du bruge den Windows Live UK konto, du oprettede tidligere.

Det var det.  Det tog mig lidt tid at rode rundt mellem for at få det til at spille, men i virkeligheden er det meget få trin, der skal til.  Så vidt jeg kan se, er det desværre nødvendigt at oprette en ny Windows Live konto, hvor man angiver UK som land, da det land, der optræder i Zune profilen, er styret af ens Windows Live login.  ´

På en tidligere version af dotninjas.dk kunne man finde en artikel om hvordan man kompilerer et udtryk til funktioner som kan bruges som plugin i egne beregninger - ved at benytte den indbyggede C# compiler. Jeg vil forsøge at genopfriske lidt for artiklen, men vil denne gang compile Lambda udtryk som f.eks.

   1:  x => x * 2;

Vores specialiserede interface erstattes nu af Expression<T> (i dette tilfælde Expression<Func<double, double>>). Dette er naturligvis intressant hvis man ønsker at arbejde videre med expression træet, f.eks. hvis man ønsker at differentiere udtrykket. Hvis man blot ønsker at beregne funktionsværdier anbefaler jeg at man returnerer Func<double, double> delegaten direkte, idet der skal kaldes Compile på et lambda udtryk for at få delegaten vi skal evaluere med - man kompiler altså i to omgange.

Grundlæggende er det dette stykke kode vi ønsker at compile runtime:

   1:  using System; 
   2:  using System.Linq.Expressions; 
   3:   
   4:  public static class LambdaParser 
   5:  { 
   6:    public static Expression<Func<double,double>> Parse() 
   7:    { 
   8:      return {expression}; 
   9:    } 
  10:  } 

Vi skal blot erstatte {expression} med det ønskede udtryk som skal være en funktion med et enkelt argument af type double og return værdi double, eller f:R->R som man siger på matematisk. Man kunne også lægge sig fast på at variablen hed x og erstatte {expression}; med x=>{expression};

Jeg har valgt C# men man kan også vælge VB eller et andet sprog hvis blot man har en passende CodeDomProvider.

   1:  CSharpCodeProvider provider = new CSharpCodeProvider(); 

Brug evt. en Dictionary med CompilerVersion="v3.5" hvis du har behov for en specific version, ellers brug config filen. Ellers skal vi blot definere vores referencer og kompile koden:

   1:  CompilerParameters cp = new CompilerParameters(); 
   2:  cp.GenerateInMemory = true; 
   3:  cp.ReferencedAssemblies.Add("System.Core.dll"); 
   4:   
   5:  CompilerResults cr = provider.CompileAssemblyFromSource(cp, code); 

Husk at checke for kompilefejl (se cr.Errors.HasErros), for der bliver ikke kastet en exception. Herfra er der 3 linjer kode til vi står med vores lambda udtryk:

   1:  Type parserType = cr.CompiledAssembly.GetType("LambdaParser"); 
   2:  MethodInfo mi = parserType.GetMethod("Parse"); 
   3:  var f = (Expression<Func<double,double>>)mi.Invoke(null, new object[] { })); 

Jeg lader det være op til læseren at indkapsle koden i en genbrugelig klasse og tilføje bells and whistles. Ekstra referencer vil være en oplagt mulighed for udvidelser. En anden oplagt mulighed er at indføre en potens funktion hvad man skal bruge matematiske formler. Det kræver dog at man parser udtrykket lidt mere end blot en søg og erstat, f.eks.

   1:  x+x^2 -> x + Math.Pow(x, 2) 

Her skal Math.Pow funktionen indsættes efter plus tegnet, mens den skal indsættes efter parentens i dette udtryk

   1:  (x+1)^2 -> Math.Pow(x+1,2) 

Heldigvis skal de øvrige operatorer behandles ens, så det kun er parenteser vis skal ta' højde for.

   1:  public static string ReplacePower(string s) 
   2:  { 
   3:    int powerIndex = s.IndexOf('^'); 
   4:    if (powerIndex > 0) 
   5:    { 
   6:      string right = s.Substring(powerIndex + 1); 
   7:      string left = s.Substring(0, powerIndex); 
   8:   
   9:      int leftStart; 
  10:      int rightEnd; 
  11:   
  12:      if (left[left.Length - 1] == ')') 
  13:      { 
  14:        leftStart = FindStartParenthesis(left) - 1; 
  15:      } 
  16:      else 
  17:      { 
  18:        leftStart = left.LastIndexOfAny(opChars); 
  19:      } 
  20:      if (right[0] == '(') 
  21:      { 
  22:        rightEnd = FindEndParenthesis(right); 
  23:      } 
  24:      else 
  25:      { 
  26:        rightEnd = right.LastIndexOfAny(opChars); 
  27:      } 
  28:   
  29:      string s0 = left.Substring(0, leftStart + 1); 
  30:      string s1 = left.Substring(leftStart + 1); 
  31:      string s2 = rightEnd >= 0 ? right.Substring(0, rightEnd) : right; 
  32:      string s3 = rightEnd >= 0 ? right.Substring(rightEnd) : string.Empty; 
  33:   
  34:      return ReplacePower(string.Format("{0}Math.Pow({1},{2}){3}", s0, s1, s2, s3)); 
  35:    } 
  36:    return s; 
  37:  } 
  38:   
  39:  private static char[] opChars = new char[] { '+', '-', '/', '*', ' ', ',' }; 
  40:   
  41:  private static int FindStartParenthesis(string s) 
  42:  { 
  43:    int index = s.Length - 1; 
  44:    int parCount = 0; 
  45:   
  46:    while (index >= 0) 
  47:    { 
  48:      if (s[index] == ')') parCount++; 
  49:      if (s[index] == '(') parCount--; 
  50:      if (parCount == 0) return index; 
  51:      index--; 
  52:    } 
  53:    return index; 
  54:  } 
  55:   
  56:  private static int FindEndParenthesis(string s) 
  57:  { 
  58:    int index = 0; 
  59:    int parCount = 0; 
  60:   
  61:    while (index < s.Length) 
  62:    { 
  63:      if (s[index] == ')') parCount--; 
  64:      if (s[index] == '(') parCount++; 
  65:      if (parCount == 0) return index; 
  66:      index++; 
  67:    } 
  68:    return index; 
  69:  } 

Med FindEndParenthesis metoden skulle det også være nemt at erstatte diverse matematisk funktioner
med deres System.Math ekvivalenter som cos -> Math.Cos og sin -> Math.Sin.

Det var lide kode til at komme igang med din egen runtime expression parser - uden at der er nogen garanti for at det virker. Husk i det mindste at indsætte diverse checks så det ikke brager ned midt i en vigtig beregning.

Her i sommervarmen var det måske på tide at lege lidt med lambda udtryk: 

Expression<Func<double,double>> f = x => x + 1;

Hvis vi ser på lambda udtryk med matematiske briller vil vi ofte gerne differentiere udtrykket for f.eks. at finde maksimum eller nulpunkter. Differentialregning er heldigvis noget med en masse regler og så hårdt arbejde for resten - hvilket jo er computerens speciale. Men hvor nemt er det egentligt at differentiere et lambda udtryk?

Ovenstående udtryk parses til en Expression-træstruktur, som er et standard abstrakt syntaks træ. De enkelte noder i træer repræsenterer operatorer (plus, minus, gange og dividere, men også større end og mindre end), tal (konstanter) og symboler (også kaldet parametre som x ovenfor). Der er en helt række af expression typer (se ExpressionType for komplet liste), men noget kortere liste at klasser som nedarver Expression.

Mange ExpressionType værdier bruger f.eks. BinaryExpression - det gælder bl.a. Add, Subtract, Multiply og Divide, men også diverse boolske operatorer som f.eks. LessThan og GreaterThan (bemærk dog at disse returner bool og ikke double).

Expression finder du i System.Linq.Expression men benyttes også af Dynamic Language Runtime (DLR). Det er specielt udtryk til at styre flow i et program og tildeling af værdier som benyttes af DLR, men det vil vi ignorere i et matematisk setup.

Vi vælger at implementere Derive som en extension metode:

public static Expression Derive(this Expression e, string parameterName)
{
  switch (e.NodeType)
  {
    case ExpressionType.Add:
      ...
      break;
    
    ...
  }
}

Vi skal naturligvis implementere regler for alle værdier ExpressionType som giver matematisk mening, men først lige en metode mere.

public static Expression Derive(this Expression e, string parameterName)
{
  return Expression.Lambda(e.Body.Derive(parameterName), e.Parameters);
}

Denne metode sikrer at vi kan benytte Derive på LambdaExpression og at resultat også bliver en LambdaExpression. Vi har altså vores afledte giver som:

Expression> df = f.Derive("x");

I vores eksempel indgår tre node typer: Parameter, Constant og Add

case ExpressionType.Parameter:
  {
    ParameterExpression pe = (ParameterExpression)e;
    return Expression.Constant(pe.Name == parametername ? 1.0 : 0.0)
  }
  
case ExpressionType.Constant:
  return Expression.Constant(0.0);
  
case ExpressionType.Add:
  {
    BinaryExpressiob be = (BinaryExpression)e;
    return Expression.Add(
      be.Left.Derive(parameterName),
      be.Right.Derive(parameterName)
    );
  }

Hvilket giver os

df = x => 1 + 0;

Bemærk, at vi arbejder med doubles hele vejen igennem, ellers fejler programmet runtime ved at der ikke findes en add-operator som tager int som det ene argument og double som det andet. Resultat fra vores Derive metode er korrekt, men ikke specielt pænt at se på og måske heller ikke optimalt rent beregningsmæssigt. Til det formål foreslår jeg endnu en ekstension metode:

public static Expression Simplify(this Expression e)
{
  ...
}

Men den øvelse gemmer vi til en anden god gang - der er endnu meget arbejde på Derive.

En anden ting man bør bemærke er at der oprettes nye Expression når man skal redigere et udtryk. Det er et underliggende princip at Expression instanserne ikke kan ændres (de er immutable), hvilket man udnytter til Multiply operatoren i Derive:

case ExpressionType.Multiply: // (fg)'=f'g+fg'
  {
    BinaryExpression be = (BinaryExpression)e;
    Expression dleft = be.Left.Derive(parameterName);
    Expression dright = be.Right.Derive(parameterName);
    return Expression.Add(
      Expression.Multiply(dleft, be.Right),
      Expression.Multiply(be.Left, dright)
    );
  }

Vi har altså genbrugt undernoderne i BinaryExpression til et nyt udtryk. Jeg vil overlade det til læseren at implementere Subtract, Divide og Negate (UnaryExpression).

For funktions kald findes typen Call og klassen MethodCallExpression som med Member (MethodInfo) giver os en reference til den funktion som skal kaldes. Det er op til os at implementere den afledte af forskellige kendte funktioner, f.eks.

(Math.Sin)':
  return Expression.Call(null, typeof(Math).GetMethod("Cos"), me.Arguments);
(Math.Cos)':
  return Expression.Negate(Expression.Call(null, typeof(Math).GetMethod("Sin"),   me.Arguments);

hvor me er vores originale MethodCallExpression og me.Arguments således er de originale argumenter til funktionskaldet - jeg lader det være op til læseren at implementere kædereglen ((f(g(x)))'=f'(g(x))*g'(x)).

Endnu en stor ting fra en matematisk vinkel er at implementere potensreglen. C# har ikke nogen potens operator (^ eller ** i andre sprog), så vi skal se på ExpressionType.Call hvor metoden er Math.Pow. Den generelle potens regel er (f^g)'=f^g*(g'ln f+(g/f)*f'), men hvis g er et tal så er det den noget simplere udgave (x^n)'=n*x^(n-1):

Expression e1 = Expression.Multiply(
  me.Arguments[1], // n
  Expression.Power(
    me.Arguments[0], // x
    Expression.Subtract(me.Arguments[1], Expression.Constant(1.0)) // -1
    )
  );

dertil kommer så kædereglen:

return Expression.Multiply(e1, me.Arguments[0].Derive(parameterName));

Bemærk, at Expression.Power(...) funktionen giver os ExpressionType.Power, så hvis resultat skal differentieres igen, så skal vi også tage højde for det. Alternativt kan man benytte Expression.Call med metoden me.Method.

Det skulle være lidt til at komme igang med noget differentialregning. Heldigvis skal man "bare" følge nogle matematiske regler for at få resultatet - som måske kræver en del simplifisering for at se pænt ud, men kompileren dømmer os ikke på skønhed.

Det er fredag, så her er et par party tricks til at vise frem over fredagsøllen :-) De viste tricks er tyvstjålet fra en kollegas præsentation på et internt gå-hjem-møde..

Testdata indenfor en given kategori kan hurtigt genereres således (til ære for ne0san vælger vi banker..)

 

Hold CTRL nede og træk i nedre højre hjørne, og vupti..

 

Skal man have lidt hjælp til oversættelsen kan dette også klares direkte fra regnearket;

 

Jeg er egentlig fint tilfreds med de farver Visual Studio 2008/2010 anvender til syntax farvning, men hvis man er til andre farver kan man jo starte med andres farveschemaer. ScottGu twittede et tip om studiostyles.info, hvor man kan dele schemaer.

Et par af schemaerne mindede mig om mine første programmeringssessioner i folkeskolens edb-lokale, fx det monokrome PDP-11

Eller de klassiske Commodore 64 farver;

Jeg har lidt overvejet en af schemaerne med sort baggrund - de ser jo rigtig kodeagtig cool ud, men bliver man ikke træt i øjnene af at se på en sort skærm?

Diabetikere bør (i hvert fald i perioder) føre dagbog over blodsukkermålinger som minimum foretaget før og efter hvert hovedmåltid og umiddelbart før sengetid.  Samtidig skal man tælle kulhydrater og løbende holde styr på sammenhængen mellem kulhydratindtaget og antallet af insulinenheder, man injicerer.

I anledning af halvårsdagen for konstateringen af min type 1 diabetes, har jeg arbejdet på et website til online registrering af blodsukkermålinger samt af indtag af insulin og kulhydrater.  Udover at give mig selv og andre diabetikere et centralt sted at føre dagbog, er det også mit første rigtige “live” ASP.NET MVC 2 og .NET 4.0 projekt.

Bayer Diabetes lever bl.a. af at lave blodsukkerapparater.  Hvert år udbyder de en sum penge til projekter, som på en eller anden måde gør livet nemmere for diabetikere, og mit website er blevet valgt som en af tre finalister til at modtage hjælp fra deres fond.  Den endelige vinder findes ved afstemning, hvor alle interesserede kan stemme.

Derfor beder jeg dig, kære læser, om at gå ind på og stemmemit bidrag.

Plz, plz, plz

P.S. Mit website er endnu ikke online, men du kan se et par skærmbilleder herunder.

simplewins1 simplewins2 simplewins3