Normalt forventer man, at hvis a + b giver c, så vil a + b altid give c.  I programmering skal man have en vis tolerance mht. præcision, når man bruger doubles og floats, men hvis a + b giver c i et program, forventer man samme resultat, uanset hvornår man laver beregningen indenfor samme proces.

Sådan troede jeg det var indtil fornylig, hvor én af vores unit tests fejlede – tilsyneladende lidt tilfældigt.  For kørte jeg den pågældende unit test alene, var der ingen fejl.  Kørte jeg den sammen med de andre unit tests i projektet, fejlede den.  Jeg fik ret hurtigt identificeret, hvilke andre unit tests, der drillede, og den fejlende unit test havde absolut intet med de andre unit tests at gøre.  Der var ingen fælles kode.

For nemheds skyld vil jeg kalde den fejlende unit test for A, og kalde de andre unit tests, som forårsagede fejlen i A for B. 

Test A tester kode indeholdende komplekse beregninger (Nelder-Mead), og fejlen gjorde, at det endelige resultat endte med en difference på 0.5!  Det er meget i min verden.

Test B er lidt speciel.  Den tester funktionalitet til at indlæse data fra et regneark.  Dvs. koden, som B tester, benytter Microsoft’s ACE driver. Efter en del test af denne driver, og noget der ligner to dages riven i mit eget hår, var jeg i stand til at genskabe problematikken med et simpelt eksempel, hvor resultat af et gangestykke med doubles gave forskellige resultater før og efter kald til ACE driveren.

Et spørgsmål på StackOverflow affødte en forklaring: “… unmanaged code may be tinkering with the FPU control word and change the way it calculates”.  Der blev også foreslået en løsning, nemlig et kald til _fpreset, som “resets the floating point package”.

Den foreslåede løsning virker, men jeg føler mig ikke overbevist om, at jeg egentlig har lyst til at bruge ACE driveren direkte i vores produktionskode.  En rådslagning med Daniel (også kendt er på sitet som ne0san) førte frem til en anden løsning, nemlig at spawn en ny process, som indlæser data fra regnearket, og kommunikerer data tilbage til hovedprocessen vha. named pipes (måske et emne for en kommende dotninjas blog).  Det virker.  Om det er en bedre løsning end _fpreset er svært at sige.

Da async/await pattern blev introduceret med C# 5, blev hele .NET frameworket gennemarbejdet, så relevante asynkrone metoder blev awaitable og fik Task som returværdi.

Det gælder ikke Windows Phone API’en.  Specielt gælder det ikke for WebClient, der i .NET 4.5 bl.a. har fået DownloadDataTaskAsync, der returnerer en Task, og dermed er awaitable.  Til Silverlight må man nøjes med DownloadDataAsync, som er baseret på Event-Based Asynchronous Pattern (EAP) gennem DownloadDataCompleted.

I System.Threading.Tasks namespacet findes en klasse kaldet TaskCompletionSource.  Denne klasse er netop beregnet til EAP scenarier, fordi den opretter en task, som man kan sætte op til at vente på, at en event bliver kaldt.  Dette gøres vha. af SetResult og SetException.  Kalder man en af de to metoder, vil den tilhørende Task afsluttes.  Vha. af TaskCompletionSource har jeg lavet følgende awaitable extension metoder til WebClient, som gør det muligt at lave awaitables for hver af de 4 HTTP metoder GET, POST, PUT og DELETE.  Metoderne benytter JSON via Json.NET:

    public static class WebClientExtensions
    {
        public static Task<T> GetAsync<T>(this WebClient client, Uri uri)
        {
            var tcs = new TaskCompletionSource<T>();
            client.DownloadStringCompleted += (s, e) =>
                {
                    if (e.Error == null)
                    {
                        T result = JsonConvert.DeserializeObject<T>(e.Result);
                        tcs.SetResult(result);
                    }
                    else
                    {
                        tcs.SetException(e.Error);
                    }
                };
            client.DownloadStringAsync(uri);
            return tcs.Task;
        }

        public static Task PostAsync<T>(this WebClient client, Uri uri, T item)
        {
            var tcs = new TaskCompletionSource<string>();
            client.Headers["Content-Type"] = "application/json";
            client.UploadStringCompleted += (s, e) =>
            {
                if (e.Error == null)
                {
                    tcs.SetResult(e.Result);
                }
                else
                {
                    tcs.SetException(e.Error);
                }
            };

            string data = JsonConvert.SerializeObject(item);

            client.UploadStringAsync(uri, "POST", data);
            return tcs.Task;
        }

        public static Task PutAsync<T>(this WebClient client, Uri uri, T item)
        {
            var tcs = new TaskCompletionSource<string>();
            client.Headers["Content-Type"] = "application/json";
            client.UploadStringCompleted += (s, e) =>
            {
                if (e.Error == null)
                {
                    tcs.SetResult(e.Result);
                }
                else
                {
                    tcs.SetException(e.Error);
                }
            };

            string data = JsonConvert.SerializeObject(item);

            client.UploadStringAsync(uri, "PUT", data);
            return tcs.Task;
        }

        public static Task DeleteAsync(this WebClient client, Uri uri)
        {
            var tcs = new TaskCompletionSource<string>();
            client.UploadStringCompleted += (s, e) =>
            {
                if (e.Error == null)
                {
                    tcs.SetResult(e.Result);
                }
                else
                {
                    tcs.SetException(e.Error);
                }
            };

            client.UploadStringAsync(uri, "DELETE", "");
            return tcs.Task;
        }

Du kan læse mere om TaskCompletionSource hos Stephen Toub, som er en af de ypperste, når det kommer til parallelisering i .NET.

Jeg har haft stor glæde af de ovenstående WebClient extensions, men min glæde var endnu større, da jeg fornylig læste, at Microsoft arbejder på en port af HttpClient til Windows Phone, og har gjort det tilgængeligt via NuGet.  Jeg har endnu ikke prøvet det, men det virker lovende.

Kigger man i eksempler på nettet på brug af Task, ser man ofte Thread.Sleep anvendt inden i den pågældende Tasks action:

Task.Factory.StartNew(() =>
{
    // Lad være med det her
    Thread.Sleep(1000);
});

Det er sjældent, man bruger Thread.Sleep i andet end eksempelkode, men skulle det ske, må man ikke gøre det sammen med en Task som ovenfor.  Der er ikke garanteret nogen sammenhæng mellem én tråd og en Task.  Task kan skifte mellem tråde – ja, i nogle tilfælde vil en Task endda eksekvere på samme tråd som kalderen, alt efter hvordan man bruger Task.Factory.StartNew og Task.Run.

Hvis man rent faktisk ønsker at stoppe en Task i en tidsperiode, skal man i stedet for Thread.Sleep bruge Task.Delay.

Jeg bruger ofter ViewModel-first metoden når jeg anvender MVVM, dvs. jeg opretter først ViewModel instanser i koden og lader WPF om at finde de rette views for hver type af view model.

WPF har en DataTemplate som giver mulighed for at knytte views til mine view models:

<DataTemplate DataType="{x:Type vm:AlphaViewModel}">
  <view:AlphaView />
</DataTemplate> 

Lad os for eksemplet skyld antage at vi har en abstrakt GreekViewModel og et antal view models som nedarver fra denne, f.eks. AlphaViewModel, BetaViewModel, GammaViewModel, etc. Til hver view model har vi så et view, f.eks. AlphaView, BetaView, GammaView, etc. For hver view model anvender vi en DataTemplate for at knytte view model og view sammen. Lad os for ekspemplets skyld binde vores græske herligheder til en listbox.

<ListBox ItemsSource={Binding Greeks} />

Antallet af DataTemplates stiger hver gang vi tilføjer en ny view model, men for folk med god ordenssand (eller OCD) er der heldivis en nemmere måde at knytte view model og views sammen. 

Hvis man skeler til MVC kan man forstille sig at man kan udnytte en struktur som denne:

Hvis en view model, f.eks. AlphaViewModel, ligger i ViewModels folderen, så kan jeg forvente at et tilsvarende view, f.eks. AlphaView, findes i View folderen. For at få denne adfær kan vi benytte DataTemplateSelector. Det giver os mulighed for vælge en DataTemplate til hver view model i kode. 

public class GreekDataTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var viewModelTypeName = item.GetType().FullName;
            var viewTypeName = viewModelTypeName.Replace("ViewModel", "View");
            var viewType = Type.GetType(viewTypeName);
            var template = new DataTemplate();
            template.VisualTree = new FrameworkElementFactory(viewType);
            return template;
        }
    }

GreekDataTemplateSelector ser på typen af item (i vores tilfælde en instans af en view model) og gætter på et navn for typen af viewet. I eksemplet vælger vi blot at erstattet ViewModel med View, dermed bliver "ViewModels.AlphaViewModel" til "View.AlphaView". Klassen FrameworkElementFactory bruges til at knytte view typen til en DataTemplate. Eksemplet kan nemt udvides, f.eks. skal der sker noget hvis et view ikke findes. Man kan søge efter flere forskellige navne, eller at der vælges et default view. Under alle omstændigheder skal koden opdateres med fejlcheck inden du sætter den i produktion.

Vores ListBox skal også lige opdateres for at få den ønskede effekt:

 

<ListBox ItemsSource={Binding Greeks} ItemTemplateSelector="{StaticResource GreekViewSelector}" />

 

 

Husk lige at oprette en instance af GreekDataTemplateSelector i Resources. DataTemplateSelector kan benyttes på mange forskellige klasser og giver mulighed for at udnytte hvordan koden er fysisk struktureret. 

 

Det er ikke ualmindeligt, at man ser C# kode som herunder, hvor == operatoren benyttes til at sammenligne to variable.

MyClass o1 = ...;
MyClass o2 = ...;

if (o1 == o2)
{
  // ...
}

Muligheden for at overloade == operatoren (og Equals) i C# giver problemer med ovenstående if-statement.  Når man bruger == eller Equals, skal man skal kende implementation af MyClass nøje for at vide præcis, hvad ovenstående kode gør.  Ejeren af MyClass kan tilføje eller fjerne overload af == operatoren og dermed ændre resultatet af betingelsen i ovenstående if-statement.  Det kan vel nærmest sammenlignes med, at man en dag opdager, at ikke alene kan man bruge sin bilnøgle til sin egen bil, men man kan bruge samme nøgle til alle andre Porscher i verden (forudsat at man kører Porsche).

Ikke nok med at ejeren af ovenstående skal kende detaljerne i implementationen af MyClass.  Udviklere, som skal vedligeholde ovenstående kode, skal også vide, hvad hensigten med koden egentlig er.  Er hensigten at sammenligne objektreferencer, eller er hensigten at sammenligne en eller anden form for identity defineret af MyClass?

Man kan gøre sig selv den tjeneste aldrig at bruge Equals eller == operator, hvis man ønsker at sammenligne objektreferencer.  I stedet kan man gøre hensigten mere tydelig ved altid at bruge Object.ReferenceEquals, når man ønsker at sammenligne referencer.

C++ er lidt sejere på det punkt, i det der er forskel på pointer equality og objekt equality.  Man kan ikke overloade == operatoren for pointers, og identity mellem to objekter er kun defineret, hvis man overloader == for klassen.  Det betyder, at uden en == operator overload, giver linje 7 herunder en compile fejl.  Har man sådan en overload, vil b1 være sand (formentlig – afhængig af ens overload) og b2 vil være falsk.

   1:  MyClass *t1 = new MyClass();
   2:  MyClass *t2 = new MyClass();
   3:   
   4:  t1->value = 2;
   5:  t2->value = 2;
   6:   
   7:  bool b1 = *t1 == *t2; // Kræver operator overload
   8:  bool b2 = t1 == t2;

En god tommelfingerregel siger, at man skal rydde op efter sig selv.

Således gælder det i C++, at den, som kalder new også skal kalder delete, og man skal holde sig fra at kalde delete på andres objekter.

I COM er reglen i princippet den samme men i en anden form.  Kalder man AddRef på en en COM reference, skal man også kalde Release, og man må ikke kalde Release på andres objekter.

I .NET skulle jeg mene, at reglen er det samme.  Dispose skal kaldes af den, der opretter objektet.  Men kigger man i .NET frameworket lader det til, at billedet er lidt mudret, hvilket nedenstående eksempler illustrerer.

StreamWriter kan initialiseres med en Stream reference, og kalder man Dispose på sin StreamWriter, vil den delegere Dispose kaldet videre til den underliggende Stream.  Nedenstående kode illustrerer dette.

   1:  static void Main(string[] args)
   2:  {
   3:      var fs = new FileStream(@"c:\temp\test.txt", FileMode.Create, FileAccess.Write);
   4:      WriteStringAsBytes(fs, "1");
   5:   
   6:      using (var writer = new StreamWriter(fs))
   7:      {
   8:          writer.Write("2");
   9:      }
  10:   
  11:      // Følgende linje kaster exception, fordi StreamWriter allerede
  12:      // har lukket filen.
  13:      WriteStringAsBytes(fs, "3");
  14:  }
  15:   
  16:  private static void WriteStringAsBytes(FileStream fs, string s)
  17:  {
  18:      var bytes = Encoding.Default.GetBytes(s);
  19:      fs.Write(bytes, 0, bytes.Length);
  20:  }

I linje 3 opretter vi en FileStream, som i linje 6 benyttes som den underliggende stream til en StreamWriter.  I linje 9 kaldes Dispose på vores StreamWriter, hvilket på hemmelig vis også lukker den underliggende FileStream.  Det viser sig i linje 13, hvor vi får en exception, fordi vi forsøger at skrive til en lukket fil.

I mine øjne er det en bug.

I .NET 4.5 har StreamWriter heldigvis fået en ny constructor overload, hvor man har mulighed for at angive, at den underliggende stream ikke skal lukkes ved Dispose.  Vores kodeeksempel fra før kommer så til at se lidt anderledes ud, for nu skal vi selv huske at lukke vores FileStream.

   1:  using (var fs = new FileStream(@"c:\temp\test.txt", FileMode.Create, FileAccess.Write))
   2:  {
   3:      WriteStringAsBytes(fs, "1");
   4:   
   5:      using (var writer = new StreamWriter(fs, Encoding.Default, 512, true))
   6:      {
   7:          writer.Write("2");
   8:      }
   9:   
  10:      WriteStringAsBytes(fs, "3");
  11:  }

For en god ordens skyld beholder jeg using-blokken omkring StreamWriter.

Implementeringen af SqlConnection og SqlCommand er ligeledes en blanding, men her er tingene vendt om ift. StreamWriter.  SqlCommand vil nemlig som udgangspunkt ikke kalde Dispose på den tilhørende SqlConnection, medmindre man beder om det.  Denne opførsel kan programmøren vælge at ændre på, i det der findes en overload til SqlCommand.ExecuteReader, der tager en parameter af typen System.Data.CommandBehavior.  Ved at anvende CommandBehavior.CloseConnection kan man bede om at få den underliggende SqlConnection lukket automatisk, når man også lukker sin DataReader.

På trods af ovenstående bør man generelt kunne forvente, at andre ikke finder på at kalde Dispose på ens objekter.  I samme boldgade bør man også kraftigt overveje at lade være med at lade sine interfaces nedarve IDisposable.  Gør man det, har man samtidig sagt til brugerne af ens API, at man regner med at kalde Dispose på de objekter, de skyder ind i ens kode.  Man kan tale om en leaky abstraction.

Jeg kan huske, at jeg for en del år siden hørte Don Box til afslutningssessionen ved TechEd i Barcelona fortælle om, at noget nær den største sikkerhedsrisiko i .NET var, at alle klasser pr. default ikke er “sealed”.  Jeg ved ikke, om det er en kæmpe sikkerhedsrisiko, men manden havde vel fat i noget (som han som regel har).  Nedarvning er ikke svaret på alles problemer, så det burde vel egentlig være sådan, at man aktivt skal åbne for nedarvning af ens klasse i stedet for som nu, hvor man aktivt skal lukke for det.

Men også ganske små detaljer kan give bugs, hvis man ikke tager højde for potentiel nedarvning.

Jeg stødte selv ind i en lille bug forleden, fordi jeg i min naivitet valgte at nedarve fra en klasse, der ikke var designet til nedarvning.  Der opstod heldigvis ikke noget stort brud på sikkerheden i virksomheden af den grund – i stedet fik jeg bare en almindelig null-reference exception.

Klassen, jeg nedarvede fra, indeholdt bl.a. følgende linjer:

   1:  Assembly assem = this.GetType().Assembly;
   2:  var resourceStream = assem.GetManifestResourceStream(this.GetType(), "ResourceName");

Problemet er kaldene til this.GetType().  Hvis klassen er nedarvet, vil GetType() returnere typen for den nedarvede klasse, og hvis de to klasser ikke ligger i samme assembly, vil .NET kigge i det forkerte assembly efter den angivne resource.  Så lektien må være, at man skal tænke sig nøje om for ikke at putte sealed på sin klasse.

CodeMaid er for alle os, der gør en dyd ud af at arrangere sin kode pæn og læsbar. Desværre ved vi jo også af erfaring, at man ikke skal være langt inde i et projekt før der bliver fokuseret mere at skidtet virker og kan leveres til tiden, end at koden kan læses næste gang man har næsen nede i linjerne i en given del af applikationen.

Jeg har længe brugt StyleCop til at forcere ensartet kodestil, men nogle gange er det noget trivielt at flyttet rundt på metoder, slette blanke linjer m.v. for at tilfredsstille StyleCop. Dette er heldigvis én af CodeMaids mange features, og yderligere kan metoder, felter etc. sorteres alfabetisk, så navigationen bliver endnu lettere.

Når man så er i gang med at rydde op i sin kode, så kan man også kaste et blik på den McCabe complexity score, som beregnes. Den siger noget om hvor kompleks en metode er og vil give en god indikation af hvor en refaktorering er nødvendig (eller hvor man skal starte sit code review).

Du finder CodeMaid som Visual Studio Extension her. Selvom den aktuelle version pt. er 0.4.2 så er den stabil og fungerer upåklageligt i VS2010.

I det, der efterhånden har udviklet sig til en lille følgeton om FILESTREAMs i SQL Server, er vi nu kommet til FULLTEXT søgning. For selvom SQL Server håndterer FILESTREAM data som eksterne filer, understøttes FULLTEXT søgning i filerne, og søgningen kan endda gøres afhængig af formatet på indholdet i filerne.  HTML filer er et godt eksempel på dette.

Hvis man forestiller sig et website, hvor brugeren har mulighed for at søge i en række artikler, der ligger gemt som HTML, vil brugerne af et sådant website formentlig have en forventning om, at de gennem søgningen kun søger i artiklernes egentlige tekstindhold.  De forventer ikke at kunne søge efter de HTML tags, der tilfældigvis findes i de gemte artikler.  Så hvis der søges efter “h1”, skal søgningen kun give et resultat, hvis en artikels brødtekst rente faktisk indeholder “h1”, og ikke hvis artiklen har en overskrift med et “<h1>” tag.

FULLTEXT søgning i FILESTREAMs understøtter netop et sådant scenarie.  Ved at fortælle SQL Server, hvilket format de gemte filer er i, vil SQL Server sørge for, at søgningen tilpasses filformatet.  Dette gøres ved at angive en TYPE COLUMN, når man opretter sit FULLTEXT INDEX:

   1:  CREATE TABLE [Article]
   2:  (
   3:      [ArticleId] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
   4:      [Title] [nvarchar](4000) NOT NULL,
   5:      [Content] [varbinary](max) FILESTREAM  NULL,
   6:      [FileExtension] [nvarchar](5) NOT NULL,
   7:      CONSTRAINT [PK_Article] PRIMARY KEY CLUSTERED 
   8:      (
   9:          [ArticleId] ASC
  10:      ) ON [PRIMARY] FILESTREAM_ON [FILESTREAM1]
  11:  ) ON [PRIMARY] FILESTREAM_ON [FILESTREAM1]
  12:  GO
  13:   
  14:  CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;
  15:  GO
  16:   
  17:  CREATE FULLTEXT INDEX ON Article
  18:  (
  19:      Title,
  20:      Content TYPE COLUMN FileExtension
  21:  ) KEY INDEX PK_Article;
  22:  GO

I linje 20 angives, at kolonnen FileExtension indeholder filtypen for Content, hvor artiklernes indhold ligger som FILESTREAM.  For at lade SQL Server foretage en fornuftig søgning i vores HTML artikler, skal vi bare sørge for, at kolonnen FileExtension indeholder teksten “.htm”.

SQL Server understøtter som standard en lang række formater.  Der findes et catalog view kaldet sys.fulltext_document_types, som returnerer de filformater, SQL Server understøtter.  Og flere kan tilføjes – man kan endda selv implementere nogen (i COM).

Med SQL Server 2008 forbedrede Microsoft muligheden for at gemme filer i databasen ved at introducere FILESTREAM.  Med FILESTREAM bliver filer gemt som fysiske filer på disk, mens al tilgang til filerne gik gennem T-SQL.  Dette blev gjort for at forbedre performance betragteligt ift. tidligere versioner af SQL Server, hvor BLOBs blev anvendt til at gemme filer.

Med SQL Server 2012 udbygger Microsoft FILESTREAM yderligere ved at tillade, at man tilgår de fysiske filer direkte gennem Windows I/O API’en.  Den nye funktionalitet kaldes FileTables, og det ser ret interessant ud.  FileTables tillader, at man kan tilgå filerne uden at skulle gå den tunge vej gennem transaktioner, samtidig med at filerne administreres af SQL Server, og dermed indgår i den daglige vedligehold med backups etc.  Fordi FileTables bygger på almindelige filer, tillader det også bruge af standard Windows directories.  FileTables bygger ovenpå FILESTREAM, hvillket betyder, at f.eks. FULLTEXT search virker i filerne.

Det er en længere historie at sætte FILESTREAM og derefter FileTables op i SQL Server 2012.  Når det er gjort, har SQL Server oprettet et fil share på serveren, som man skal bruge til at tilgå filerne, hvis man ønsker at bruge Windows I/O API’en.  Funktionen FileTableRootPath bruges til at få stien til dette share:

   1:  SELECT FileTableRootPath();

For at benytte FileTables kræves en speciel tabel databases.  Denne tabel indeholder en række for hver fil, der gemmes i det oprettede fil share.  Det betyder, at SQL Server intercepter alle I/O til dette fil share.  Filsystemet i Windows understøtter som sådan ikke ACID transaktioner, men så vidt jeg kan forstå, benytter SQL Server diverse locks på tabellen til at styre concurrency

Med et share og en FileTable på plads, kan man nu meget nemt skrive filer til databasen uden brug af SQL (men husk – det er uden en transaktion):

   1:  string rootShare = @"\\machine\blabla";
   2:  for (int i = 0; i < 10; i++)
   3:  {
   4:      File.WriteAllLines(rootShare + String.Format("\\TextFile_{0}.txt", i), new string[] { "Hello " + i });
   5:  }

Følgende SQL afslører, at der rent faktisk er dukket 10 rækker op i vores FileTable:

   1:  SELECT file_stream.GetFileNamespacePath()
   2:  FROM MyFileTable

FileTables er tilgængeligt i den SQL Server 2012, der er til download d.d. (RCO hedder den vist) – og det er også tilgængeligt i Express edition. Sweet.