Programmieren mit F#

Erstellen von MVVM-Anwendungen in F#

Chris Marinos

Beispielcode herunterladen.

Obwohl F# ein neues Mitglied der Visual Studio-Familie ist, hat es bereits zahlreichen .NET-Entwicklern geholfen, die Leistungsfähigkeit der funktionalen Programmierung zu entdecken. F# ist dafür bekannt, komplizierte Probleme wie die parallele und asynchrone Programmierung, die Verarbeitung von Daten sowie Finanzmodelle zu vereinfachen. Dies bedeutet jedoch nicht, dass es sich bei F# um eine Nischensprache handelt. Die Sprache kann auch zur Lösung alltäglicher Probleme beitragen.

In diesem Artikel erfahren Sie, wie Sie mittels F# nützliche Silverlight- und Windows Presentation Foundation (WPF)-Model-View-ViewModel (MVVM)-Anwendungen erstellen können. Sie werden erkennen, dass die gleichen Konzepte, die F# zu einem so hervorragenden Tool für die Vereinfachung komplizierter Algorithmen machen, auch zur Reduzierung des Aufwands für Ihre Ansichtsmodelle beitragen können. Außerdem zeigen wir Ihnen, wie die bekannten asynchronen Workflows in F# in einer GUI-Umgebung angewendet werden können. Schließlich werde ich zwei häufig verwendete Ansätze für die Strukturierung von MVVM-Anwendungen in F# zusammen mit ihren Vor- und Nachteilen vorstellen. Am Ende dieses Artikels werden Sie verstehen, dass F# mehr als nur ein Tool zur Lösung spezieller Probleme ist. Sie werden F# verwenden können, um auch die einfachsten Anwendungen leichter zu lesen, zu schreiben und zu warten.

Reduzieren des Aufwands

Sie denken vielleicht, dass F# eine merkwürdige Wahl für GUI-Anwendungen darstellt. Funktionale Sprachen entfernen den Fokus von Nebeneffekten, und Frameworks wie Silverlight und WPF verfügen über zahlreiche Nebeneffekte. Vielleicht denken Sie, dass Ansichtsmodelle nicht genügend komplizierte Logik enthalten, sodass der Wechsel zur funktionalen Programmierung keine Vorteile bietet. Vielleicht denken Sie auch, dass die funktionale Programmierung einen Paradigmenwechsel voraussetzt, der das Arbeiten mit Entwurfsmustern wie MVVM erschwert. Die Wahrheit ist, dass die Verwendung von F# für die Erstellung von Ansichtsmodellen so einfach wie die Verwendung von C# ist. Ansichtsmodelle stellen außerdem eine gute Möglichkeit dar, zu zeigen, wie F# aufwendigen Standardcode reduziert. Sie werden überrascht sein, wie effektiv F# das Signal-Rausch-Verhältnis in Ihrem Code verbessert, wenn Sie gewohnt sind, mit C# zu programmieren.

Abbildung 1 enthält F#-Code für ein einfaches Filmmodell mit Feldern für den Namen, das Genre und eine optionale Bewertung. Wenn Sie mit Optionstypen in F# nicht vertraut sind, können Sie sich diese als eine leistungsfähigere und prägnantere Analogie zu den NULL-Werten in C# vorstellen. Ein Optionstyp ist eine natürliche Art zu sagen, dass ein Filmmodell optional über eine Bewertung verfügen kann, kann jedoch Probleme für die Datenbindung verursachen. Wenn Sie z. B. versuchen, den Wert eines Optionstyps auf Keine zu setzen (der Entsprechung für NULL bei NULL-Werten), wird eine Ausnahme ausgelöst. Sie möchten vielleicht auch das Steuerelement für die Bewertung ausblenden, wenn die Option auf Keine festgelegt ist. Wenn in WPF oder Silverlight eine Ausnahme ausgelöst wird, wenn der Wert für die Bewertung abgerufen wird, wird möglicherweise die Sichtbarkeitsbindung nicht korrekt ausgeführt. Im Folgenden sehen Sie ein einfaches Beispiel dafür, an welchen Stellen Sie einem Ansichtsmodell zusätzliche Anzeigelogik für die Bewertungen hinzufügen müssen.

Abbildung 1 Ein einfaches Filmmodell

type Movie = {
  Name: string
  Genre: string
  Rating: int option
}

Abbildung 2 zeigt ein Ansichtsmodell mit einem Beispiel für diese zusätzliche Anzeigelogik. Wenn die Bewertung vorhanden ist, wird der Wert an die Anzeige weitergegeben. Andernfalls erhält die Bewertung per Voreinstellung den Wert 0. Diese einfache Logik verhindert, dass Ausnahmen ausgelöst werden, wenn die Bewertung den Wert "Keine" hat. Das Ansichtsmodell verfügt außerdem über eine zweite Eigenschaft für die Behandlung der Sichtbarkeitsbindung. Diese Methode gibt einfach "True" zurück, wenn eine Bewertung vorhanden ist, und "False", wenn keine Bewertung vorhanden ist. Die Logik dieses Ansichtsmodells ist einfach, steht jedoch nicht im Vordergrund dieses Beispiels. Betrachten Sie stattdessen, wie prägnant F# die Definition des Ansichtsmodells ausdrückt. In C# enthalten Ansichtsmodelle häufig enorme Mengen an Standardcode in der Ansichtslogik, der nur dazu dient, den Compiler glücklich zu machen.

Abbildung 2 Ansichtsmodell für einen Film mit Anzeigelogik für Bewertungen

type MovieViewModel(movie:Movie) =
  member this.Name = movie.Name
 
  member this.Genre = movie.Genre
 
  member this.Rating =
    match movie.Rating with
    | Some x -> x
    | None -> 0
 
  member this.HasRating = movie.Rating.IsSome

Abbildung 3 zeigt das gleiche Ansichtsmodell in C#. Die Zunahmen an Standardcode ist dramatisch. Sie benötigen ungefähr viermal so viele Codezeilen in C#, um das Ansichtsmodell auszudrücken, als in F#. Einen großen Teil dieser Zunahme nehmen geschweifte Klammern ein. Aber auch wichtige Objekte wie Typanmerkungen, Rückgabeanweisungen und Zugreifbarkeitsmodifizierer machen es schwer, die Logik zu verstehen, die das Ansichtsmodell einkapseln soll. F# reduziert dieses Rauschen und setzt den Fokus auf die wichtigen Teile des Codes. Die funktionale Programmierung steht manchmal in dem Ruf, extrem knapp und nur schwer lesbar zu sein. In diesem Beispiel wird jedoch deutlich, dass F# die Klarheit nicht zugunsten der Kürze opfert.

Abbildung 3 Ansichtsmodell für Filme in C#

class MovieViewModelCSharp
{
  Movie movie;
 
  public MovieViewModelCSharp(Movie movie)
  {
    this.movie = movie;
  }
 
  public string Name
  {
    get { return movie.Name; }
  }
 
  public string Genre
  {
    get { return movie.Genre; }
  }
 
  public int Rating
  {
    get
    {
      if(OptionModule.IsSome(movie.Rating))
      {
        return movie.Rating.Value;
      }
      else
      {
        return 0;
      }
    }
  }
 
  public bool HasRating
  {
    get
    {
      return OptionModule.IsSome(movie.Rating);
    }
  }
}

Das vorangegangene Beispiel zeigt einen der Vorteile, die F# für das Schreiben von Ansichtsmodelle bietet. F# behebt jedoch auch ein häufiges Problem mit Ansichtsmodellen in MVVM-Anwendungen. Nehmen Sie an, dass Ihre Domäne geändert wurde und Ihr Modell aktualisiert werden muss. Das Genre besteht nun aus einer Liste von Tags und nicht aus einer einzelnen Zeichenfolge. Im Modellcode bedeutet dies, dass die folgende Zeile:

Genre: string

Zu dieser Zeile geändert wird:

Genre: string list

Da diese Eigenschaft der Anzeige jedoch mittels des Ansichtsmodells mitgeteilt wurde, muss auch der Rückgabewert im Ansichtsmodell geändert werden. Dies erfordert eine manuelle Änderung für das Ansichtsmodell in C#, wird in F# jedoch aufgrund der Typeninferenz automatisch durchgeführt. Dies ist vielleicht überraschend, wenn Sie nicht mit der Typeninferenz in F# vertraut sind. Es ist jedoch genau das Verhalten, das Sie wünschen. Das Genre erfordert keine Anzeigelogik. Daher übergibt das Ansichtsmodell dieses Feld an die Anzeige, ohne es zu ändern. Mit anderen Worten, der Rückgabetyp der Eigenschaft im Ansichtsmodell ist nicht wesentlich, solange dieser dem Rückgabetyp der Eigenschaft des Modells entspricht. Das ist genau das, was der F#-Code leistet. Denken Sie daran, dass F# weiter statisch eingegeben wird. Jede falsche Verwendung des Felds im Ansichtsmodell oder Modell (abgesehen von XAML) führt zu einem Fehler während der Kompilierung.

Nutzung vorhandener Ressourcen

Die Anzeigemodelle in Abbildung 1 und Abbildung 2 unterstützen nur die einseitige Bindung, da sie nur für das Hinzufügen der Anzeigelogik zum Modell verantwortlich sind. Diese einfachen Anzeigemodelle sind nützlich, um die Fähigkeit von F# zu zeigen, Standardcode zu reduzieren. Ansichtsmodelle müssen in der Regel jedoch die beidseitige Bindung unterstützen, indem INotifyPropertyChanged für veränderliche Eigenschaften implementiert wird. Häufig enthalten C#-MVVM-Anwendungen eine Ansichtsmodell-Basisklasse, um die Implementierung von INotifyPropertyChanged und anderen Ansichtsmodellfunktionen zu vereinfachen. Möglicherweise haben Sie Bedenken, dass Sie dieses Verhalten erneut in F# implementieren müssen. F# ermöglicht Ihnen jedoch die Wiederverwendung vorhandener C#-Ansichtsmodell-Basisklassen, sodass Sie diese nicht neu schreiben müssen.

Abbildung 4 zeigt die Verwendung der Klasse ViewModelBase in F#. ViewModelBase ist eine C#-Basisklasse, die von Brian Genisio (houseofbilz.com) geschrieben wurde. Ich verwende sie für alle von mir erstellten C#- und F#-Ansichtsmodelle. In Abbildung 4 stellt die Basisklasse die Funktionen base.Get und base.Set bereit, die für die Implementierung von INotifyPropertyChange verwendet werden. ViewModelBase unterstützt außerdem die konventionsbasierte Befehlsgenerierung unter Verwendung der dynamischen Programmierfunktionen von C#. Beide Funktionen funktionieren problemlos in F#-Ansichtsmodellen, da F# für die einfache Interoperabilität mit anderen .NET-Sprachen entwickelt wurde. Sie finden die Quelle unter viewmodelsupport.codeplex.com. Dort erhalten Sie weitere Informationen zur Verwendung von ViewModelBase.

Abbildung 4 Ererbung aus einer C#-ViewModelBase-Klasse

type MainWindowViewModel() =
  inherit ViewModelBase()
 
  member this.Movies
    with get() =
      base.Get<ObservableCollection<MovieViewModel>>("Movies")
 
    and set(value) =
      base.Set("Movies", value)

Erzielen von Asynchronität

Die Unterstützung asynchroner und abbrechbarer Prozesse ist eine weitere häufige Anforderung an Ansichtsmodelle und Modelle. Die Erfüllung dieser Anforderung mittels traditioneller Techniken kann Ihrer Anwendung eine erhebliche Komplexität hinzufügen. F# enthält jedoch leistungsfähige Funktionen für die asynchrone Programmierung, die diese Aufgabe vereinfachen. Abbildung 5 zeigt den synchronen Aufruf eines Webdiensts, um Filmdaten abzurufen. Die Antwort des Servers wird als Liste von Filmmodellen analysiert. Die Modelle in dieser Liste werden dann in Ansichtsmodelle projiziert und einer ObservableCollection hinzugefügt. Die Daten dieser Auflistung sind an ein Steuerelement in der Anzeige gebunden, um dem Benutzer die Ergebnisse anzuzeigen.

Abbildung 5 Ein Beispiel für eine Webanfrage zur Verarbeitung von Filmen

member this.GetMovies() =
  this.Movies <- new ObservableCollection<MovieViewModel>()
 
  let response = webClient.DownloadString(movieDataUri)
 
  let movies = parseMovies response
 
  movies
  |> Seq.map (fun m -> new MovieViewModel(m))
  |> Seq.iter this.Movies.Add

Die Konvertierung dieses Codes für die asynchrone Ausführung würde die vollständige Überarbeitung des Steuerflusses mittels traditioneller asynchroner Bibliotheken erfordern. Sie müssten den Code in getrennte Rückrufmethoden für die einzelnen asynchronen Aufrufe aufteilen. Dies fügt Komplexität hinzu und macht den Code schwerer verständlich. Außerdem wird der Wartungsaufwand für den Code erheblich vergrößert. Das F#-Modell kennt diese Probleme nicht. In F# können Sie das asynchrone Verhalten implementieren, indem Sie einige kleine Änderungen durchführen, die sich nicht auf die Struktur des Codes auswirken, wie in Abbildung 6 gezeigt.

Abbildung 6 Asynchrone Webanfrage zur Verarbeitung von Filmen

member this.GetMovies() =
  this.Movies <- new ObservableCollection<MovieViewModel>()
 
  let task = async {
    let! response = webClient.AsyncDownloadString(movieDataUri)
 
    let movies = parseMovies response
 
    movies
    |> Seq.map (fun m -> new MovieViewModel(m))
    |> Seq.iter this.Movies.Add
  }
 
  Async.StartImmediate(task)

Das Beispiel in Abbildung 6 zeigt den gleichen Code in asynchroner Ausführung. Der Code, der den Webdienst aufruft und die Ergebnisliste aktualisiert, wird in einen asynchronen Block eingebunden, um die Änderung zu starten. Das Schlüsselwort let! wird innerhalb dieses Blocks verwendet, um F# mitzuteilen, dass eine Anweisung asynchron ausgeführt werden soll. Im Beispiel teilt let! dem Webclient mit, dass er eine Webanfrage asynchron ausführen soll. F# stellt die Methode AsyncDownloadString als Erweiterung für WebClient bereit, um diesen Prozess zu ermöglichen. Die letzte Änderung stellt der Aufruf von Async.StartImmediate dar. Dieser startet den asynchronen Block im aktuellen Thread. Die Ausführung auf dem GUI-Thread verhindert verwirrende Ausnahmen, die ausgelöst werden, wenn Sie die GUI auf einem Hintergrundthread aktualisieren. Das asynchrone Verhalten stellt sicher, dass die GUI nicht abstürzt, wenn eine Webanfrage durchgeführt wird.

Die Änderung zur asynchronen Ausführung dieses Verhaltens erforderte nicht viel zusätzlichen Code. Es ist jedoch vielleicht ebenso wichtig, dass keine Änderung an der Struktur des Codes erforderlich war. Wenn Sie den Code neu erstellen, müssen Sie sich nicht darum kümmern, diesen so zu schreiben, dass er in der Zukunft auch asynchron ausgeführt werden kann. Sie können bei der Erstellung des Prototyps synchronen Code schreiben und diesen leicht zu asynchronem Code ändern, wenn dies notwendig wird. Diese Flexibilität kann Ihnen Stunden an Entwicklungszeit einsparen. Ihre Kunden sind glücklicher, wenn Sie Änderungen schneller implementieren können.

Diese Art der asynchronen Programmierung sollte Ihnen bekannt sein, wenn Sie über die neuesten C# auf dem Laufenden sind. Der Grund hierfür ist, dass die Updates für die asynchrone Programmierung in C# im Wesentlichen auf dem F#-Modell beruhen. Die Funktionen für die asynchrone Programmierung in C# sind als Community Technology Preview (CTP) verfügbar (bit.ly/qqygW9). Die asynchronen Funktionen von F# können Sie jedoch heute bereits in der Produktion einsetzen. Asynchrone Workflows sind seit der Veröffentlichung in F# verfügbar. Diese sind daher ein gutes Beispiel dafür, warum Sie durch das Erlernen von F# ein besserer C#-Entwickler werden.

Obwohl das C#- und das F#-Modell einander ähnlich sind, gibt es einige Unterschiede, und die Abbrechbarkeit ist einer dieser Unterschiede. Der Code in Abbildung 7 fügt der Funktion GetMovies Abbrechbarkeit hinzu. Dies erfordert ebenfalls nur eine sehr kleine Änderung. Damit der Workflow die Abbrechbarkeit unterstützt, müssen Sie ein CancellationTokenSource erstellen und dessen Abbrechbarkeitstoken an die Funktion Async.StartImmediate übergeben. Abbildung 7 enthält einigen zusätzlichen Setupcode zu Beginn der Funktion GetMovies, um alle ausstehenden Prozesse abzubrechen, sodass die Observable-Auflistung nicht mehr als einmal aktualisiert wird. Außerdem wird bei jedem Aufruf der Funktion ein neues CancellationTokenSource herausgegeben, um sicherzustellen, dass jede Ausführung des Workflows über ein eindeutiges CancellationToken verfügt.

Abbildung 7 Abbrechbarkeit in F#

let mutable cancellationSource = new CancellationTokenSource()
 
member this.GetMovies() =
  this.Movies <- new ObservableCollection<MovieViewModel>()
  cancellationSource.Cancel()
  cancellationSource <- new CancellationTokenSource()
 
  let task = async {
    let! response = webClient.AsyncDownloadString(movieDataUri)
 
    let movies = parseMovies response
 
    movies
    |> Seq.map (fun m -> new MovieViewModel(m))
    |> Seq.iter this.Movies.Add
  }
 
  Async.StartImmediate(task, cancellationSource.Token)

Im C#-Modell müssen Sie manuell ein Abbrechbarkeitstoken an die Funktionskette übergeben, um die Abbrechbarkeit zu unterstützen. Dies ist ein Eingriff, der möglicherweise von Ihnen erfordert, dass Sie zahlreichen Funktionssignaturen ein zusätzliches Argument hinzufügen. Sie müssen außerdem das Abbrechbarkeitstoken manuell abrufen, um zu sehen, ob das Abbrechen angefordert wird. Das F#-Modell erfordert sehr viel weniger Arbeit. Wenn der asynchrone Workflow auf ein let! trifft, prüft er das Abbrechbarkeitstoken, das er im Aufruf StartImmediate erhalten hat. Wenn das Token gültig ist, wird der Workflow wie gewöhnlich ausgeführt. Wenn das Token ungültig ist, wird der Prozess nicht ausgeführt, und der Rest des Workflows wird ebenfalls nicht ausgeführt. Die implizite Behandlung der Abbrechbarkeit ist eine nützliche Funktion, Sie sind jedoch nicht auf diese beschränkt. Wenn Sie das Abbrechbarkeitstoken manuell für einen Workflow abrufen müssen, können Sie mittels der Eigenschaft Async.CancellationToken auf dieses zugreifen.

let! token = Async.CancellationToken

Strukturieren von MVVM-Anwendungen in F#

Da Sie nun einige der Arten kennen, wie F# Ihre Ansichtsmodelle und Modelle optimieren kann, erkläre ich Ihnen nun, wie Sie F# in Ihre Silverlight- und WPF-Anwendungen integrieren. Es gibt viele verschiedene Möglichkeiten, wie Sie Ihre MVVM-Anwendungen in C# strukturieren können. Das gleiche gilt für F#. Ich behandele in diesem Artikel zwei dieser Ansätze: den Ansatz ausschließlich mit F# und den mehrsprachigen Ansatz, bei dem C# für Anzeigen und F# für Ansichtsmodelle und Modelle verwendet wird. Ich ziehe den mehrsprachigen Ansatz aus mehreren Gründen vor. Zunächst einmal wird dieser Ansatz vom F#-Team empfohlen. Zweitens ist die Toolunterstützung für WPF und Silverlight in C# sehr viel stabiler als in F#. Schließlich ermöglicht Ihnen dieser Ansatz die Integration von F# in vorhandene Anwendungen und stellt eine Möglichkeit dar, F# für Ihre MVVM-Anwendungen mit geringem Risiko zu testen.

Der Ansatz ausschließlich mit F# ist nützlich, da Sie Ihre Anwendung in nur einer Sprache schreiben, hat jedoch einige Einschränkungen. Ich zeige Ihnen später, wie Sie einige dieser Hindernisse überwinden können. Ich bin jedoch der Ansicht, dass die Vorteile den Aufwand nur bei kleinen Anwendungen lohnen. Der mehrsprachige Ansatz verhindert, dass Sie F# für den Anzeigecode verwenden können. Gut strukturierte MVVM-Anwendungen sollten jedoch nur eine sehr begrenzte Menge an Anzeigelogik enthalten. Darüber hinaus ist C# eine hervorragend für das Schreiben von Anzeigelogik geeignete Sprache, wenn erforderlich, da zahlreiche Nebeneffekte vorhanden sind und die Sprache imperativ ist.

Der mehrsprachige Ansatz

Es ist sehr einfach, eine MVVM-Anwendung mittels des mehrsprachigen Ansatzes zu erstellen. Zunächst erstellen Sie in C# mittels der Projektvorlage für WPF-Anwendungen ein neues WPF-Projekt. Dieses Projekt ist für alle Anzeigen und jeden Codebehind verantwortlich, die bzw. den Sie für Ihre Anwendung benötigen. Als Nächstes fügen Sie der Lösung ein neues F#-Bibliotheksprojekt hinzu, das Ansichtsmodelle, Modelle und den gesamten Code enthält, der sich nicht auf die Anzeige bezieht. Schließlich müssen Sie dem F#-Bibliotheksprojekt einen Verweis aus dem C#-WPF-Projekt hinzufügen. Das ist alles, was Sie für den mehrsprachigen Ansatz benötigen.

F# wurde für die reibungslose Interoperabilität mit allen .NET-Sprachen entworfen. Das bedeutet, dass Sie F#-Ansichtsmodelle mittels aller Methoden, die Sie traditionell für C#-Ansichtsmodelle verwenden, mit C#-Anzeigen verbinden können. Ich zeige Ihnen hier ein Beispiel, in dem aus Gründen der Einfachheit Codebehind verwendet wird. Zunächst erstelle ich ein einfaches Ansichtsmodell, indem ich Module1.fs im F#-Projekt zu MainWindowViewModel.fs umbenenne. Ich fülle dann das Ansichtsmodell mit dem Code aus Abbildung 8. Ich verbinde das F#-Ansichtsmodell mittels des Codes aus Abbildung 9 mit der C#-Anzeige. Wenn Sie nicht wüssten, dass das Ansichtsmodell mit F# wurde, würden Sie hier keinen Unterschied zum C#-Ansichtsmodell erkennen können.

Abbildung 8 F#-Ansichtsmodell (Dummy)

namespace Core.ViewModels
 
type MainWindowViewModel() =
  member this.Text = "hello world!"
Figure 9 Connecting the F# View Model to the C# View
protected override void OnInitialized(EventArgs e)
{
  base.OnInitialized(e);
 
  this.DataContext = new MainWindowViewModel();
}

Abbildung 9 Verbindung des F#-Ansichtsmodells und der C#-Anzeige

protected override void OnInitialized(EventArgs e)
{
  base.OnInitialized(e);
 
  this.DataContext = new MainWindowViewModel();
}

Ich füge MainWindow.xaml ein Textfeld hinzu und lege die Bindung als Text fest. Alles verhält sich so, als wäre die Anwendung ausschließlich in C# erstellt worden. Ich stelle sicher, dass die Bindung funktioniert, indem ich die Anwendung ausführe und die Standardbegrüßung "Hallo Welt" anzeige.

Der Ansatz unter ausschließlicher Verwendung von F#

Wie bereits erwähnt, ziehe ich den mehrsprachigen Ansatz vor, da dieser einfacher zu verwenden und flexibler ist. Ich bespreche hier jedoch dennoch den Ansatz unter ausschließlicher Verwendung von F#, um Ihnen die Vor- und Nachteile zu zeigen. Visual Studio 2010 wird mit einer Vorlage für die Erstellung von Silverlight-Bibliotheken in F# ausgeliefert. Es enthält jedoch keine Vorlagen für die Erstellung von WPF- oder Silverlight-Anwendungen in F#. Glücklicherweise gibt es im Internet jedoch einige hervorragend hierfür geeignete Vorlagen. Ich empfehle Ihnen, mit den von Daniel Mohl erstellten Vorlagen zu beginnen (bloggemdano.blogspot.com), da diese Beispielanwendungen enthalten, anhand derer Sie die Struktur einer vollständigen Anwendung erkennen können. Ich zeige Ihnen, wie Sie eine WPF-Anwendung in F# von Grund auf neu erstellen können, damit Sie den Prozess kennen, empfehle Ihnen jedoch, in der Praxis eine der im Internet verfügbaren Vorlagen zu verwenden.

Ich erstelle ein neues F#-Anwendungsprojekt namens FSharpOnly, um mit der Entwicklung ausschließlich in F# zu beginnen. Ich warte, bis die Erstellung des Projekts abgeschlossen ist, und öffne anschließend die Projekteigenschaften. Dort ändere ich den Ausgabetyp zu Windows-Anwendung. Nun füge ich Verweise auf PresentationCore, PresentationFramework, PresentationUI, System.Xaml und WindowsBase hinzu. Ich füge dem Projekt Dateien namens App.xaml und MainWindow.xaml hinzu und lege die Build-Aktion für diese Dateien jeweils als "Resource" fest. Beachten Sie, dass es per Voreinstellung in F# keine Objektvorlage für die Generierung von XAML-Dateien gibt. Sie können jedoch die allgemeine Textdokumentvorlage mit der Erweiterung .xaml verwenden. Ich fülle die XAML-Dateien mit dem Code aus Abbildung 10 bzw. Abbildung 11 aus. App.xaml und MainWindow.xaml führen die gleichen Aufgaben aus, die sie auch in einer normalen WPF-Anwendung ausführen, die in C# erstellt wurde.

Abbildung 10 Beispiel einer App.xaml-Datei

<Application
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="assembly=FSharpOnly"
  StartupUri="MainWindow.xaml">
</Application>
Figure 11 A Sample MainWindow.xaml File
<Window
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="assembly=FSharpOnly"
  Title="Sample F# WPF Application Written Only in F#"
  Height="100"
  Width="100" >
  <Grid>
    <TextBlock>Hello World!</TextBlock>
  </Grid>
</Window>

Abbildung 11 Beispiel einer MainWindow-Datei

<Window
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="assembly=FSharpOnly"
  Title="Sample F# WPF Application Written Only in F#"
  Height="100"
  Width="100" >
  <Grid>
    <TextBlock>Hello World!</TextBlock>
  </Grid>
</Window>

Ich füge nun Program.fs den Code aus Abbildung 12 hinzu. Dieser Code ist für das Laden der Datei App.xaml und das Ausführen der Anwendung verantwortlich.

Abbildung 12 Beispiel für Program.fs

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Markup
 
[<STAThread>]
[<EntryPoint>]
let main(_) =
  let application = Application.LoadComponent(new Uri("App.xaml", UriKind.Relative)) :?> Application
  application.Run()

Ich führe die Anwendung aus, um die Begrüßung "Hallo Welt" anzuzeigen. An diesem Punkt können Sie entscheiden, mit welcher Technik Sie Ihre Ansichtsmodelle mit Ihren Modellen verbinden möchten, genau wie beim mehrsprachigen Ansatz.

Ein Problem, dem Sie sich bei diesem Ansatz möglicherweise gegenüber sehen, betrifft statische Ressourcen. App.xaml wird normalerweise in WPF-Projekten, die mit C# erstellt werden, als Anwendungsdefinition definiert. Beim Ansatz unter ausschließlicher Verwendung von F# wird die Datei jedoch als Ressource definiert. Dies führt zur Auflösung der statischen Ressourcen, die in App.xaml definiert sind, sodass diese zur Laufzeit zu Fehlern führen, wenn Sie diese aus anderen XAML-Dateien verwenden. Dieses Problem kann leicht umgangen werden: Ich ändere die Build-Aktion für die Datei App.xaml zu ApplicationDefinition und lade den Designer neu. Dies führt dazu, dass der Designer die Ressourcen in App.xaml erkennt und die Anzeigen korrekt lädt. Denken Sie auch daran, dass Sie App.xaml wieder als Ressource definieren müssen, wenn Sie die Anwendung erstellen. Andernfalls erhalten Sie einen Buildfehler.

Codebehind funktioniert ebenfalls im Ansatz unter ausschließlicher Verwendung von F# auf andere Weise. F# unterstützt keine partiellen Klassen. Daher können XAML-Dateien nicht mit einer .fs-Codebehind-Datei verknüpft werden, wie dies in C# der Fall ist. Es ist ein bewährtes Verfahren, Codebehind in MVVM-Anwendungen zu vermeiden, wenn möglich. Manchmal ist Codebehind jedoch der praktischste Weg, um ein Problem zu lösen. Es gibt mehrere Möglichkeiten, wie Sie das Fehlen von traditionellem Codebehind in F# umgehen können. Die direkteste Möglichkeit besteht darin, die gesamte Anzeige in F# zu konstruieren. Obwohl dies ein sehr direkter Ansatz ist, kann er aufwendig sein, da die deklarative Natur von XAML verloren geht. Der andere Ansatz besteht darin, die grafischen Elemente einzubinden, wenn die Anwendung konstruiert wird. Abbildung 13 zeigt ein Beispiel für diesen Ansatz.

Abbildung 13 Veränderte Main.fs, um Oberflächenelemente einzubinden

let initialize (mainWindow:Window) =
  let button = mainWindow.FindName("SampleButton") :?> Button
  let text = mainWindow.FindName("SampleText") :?> TextBlock
 
  button.Click
  |> Event.add (fun _ -> text.Text <- "I've been clicked!")
 
[<STAThread>]
[<EntryPoint>]
let main(_) =
  let application = Application.LoadComponent(new Uri("App.xaml", UriKind.Relative)) :?> Application
 
  // Hook into UI elements here
  application.Activated
  |> Event.add (fun _ -> initialize application.MainWindow)
 
  application.Run()

Das Fehlen von partiellen Klassen in F# erschwert auch die Nutzung von Benutzersteuerelementen. Die Erstellung von Benutzersteuerelementen in XAML ist einfach. Sie können jedoch nicht in anderen XAML-Dateien auf die Benutzersteuerelemente verweisen, da es in der Assembly keine Definition für partielle Klassen gibt. Sie können dieses Problem umgehen, indem Sie die Klasse XamlLoader erstellen, wie in Abbildung 14 gezeigt.

Abbildung 14 Klasse XamlLoader zur Erstellung von Benutzersteuerelementen in F#

type XamlLoader() =
  inherit UserControl()
 
  static let OnXamlPathChanged(d:DependencyObject) (e:DependencyPropertyChangedEventArgs) =
    let x = e.NewValue :?> string
    let control = d :?> XamlLoader
 
    let stream = Application.GetResourceStream(new Uri(x, UriKind.Relative)).Stream
    let children = XamlReader.Load(stream)
    control.AddChild(children)
 
  static let XamlPathProperty =
    DependencyProperty.Register("XamlPath", typeof<string>, typeof<XamlLoader>, new PropertyMetadata(new PropertyChangedCallback(OnXamlPathChanged)))
 
  member this.XamlPath
    with get() =
      this.GetValue(XamlPathProperty) :?> string
           
    and  set(x:string) =
      this.SetValue(XamlPathProperty, x)
 
  member this.AddChild child =
    base.AddChild(child)

Mit dieser Klasse können Sie den Pfad zu einer XAML-Datei festlegen, indem Sie eine Abhängigkeitseigenschaft verwenden. Wenn Sie diese Eigenschaft festlegen, analysiert das Ladeprogramm die XAML aus der Datei und fügt die in der Datei definierten Steuerelemente als untergeordnete Elemente von sich selbst ein. In XAML wird dies wie folgt verwendet:

<local:XamlLoader XamlPath="UserControl.xaml" />

Die Umgebung mit XamlLoader ermöglicht Ihnen die Erstellung von Benutzersteuerelementen, ohne dass Sie den mehrsprachigen Ansatz verwenden müssen. Es stellt jedoch ein Hindernis dar, dem Sie sich bei der Erstellung von Anzeigen in C# nicht gegenüber sehen.

Schlussbemerkungen

Da Sie nun F# in Aktion gesehen haben, ist es klar, dass es sich um eine Sprache für das Erstellen praktischer Anwendungen handelt. Sie haben gesehen, dass F# die Menge an Standardcode in Ihrem Code reduziert und das Lesen und Warten Ihrer Ansichtsmodelle und Modelle vereinfacht. Sie haben erfahren, wie Sie Funktionen wie die asynchrone Programmierung verwenden können, um komplizierte Probleme schnell und flexibel zu lösen. Schließlich habe ich Ihnen eine Übersicht über zwei wesentliche Ansätze für die Strukturierung von MVVM-Anwendungen in F# gegeben und Ihnen die Vor- und Nachteile für beide Ansätze gezeigt. Nun können Sie die Leistungsfähigkeit der funktionalen Programmierung in Ihren Silverlight- und WPF-Anwendungen nutzen.

Wenn Sie das nächste Mal eine Silverlight- oder WPF-Anwendung schreiben, versuchen Sie, diese in F# zu schreiben. Überlegen Sie sich, Teile einer vorhandenen Anwendung mittels des mehrsprachigen Ansatzes in F# zu erstellen. Sie werden schnell eine erhebliche Reduzierung der Menge an Code feststellen, den Sie schreiben und warten müssen. Rollen Sie die Ärmel hoch und befassen Sie sich mit dem Ansatz unter ausschließlicher Verwendung von F#. Sie werden mit Sicherheit etwas lernen, was Sie noch nicht über WPF, Silverlight oder F# wussten. Unabhängig davon, was Sie als Nächstes tun – wenn Sie einmal mit F# programmiert haben, werden Sie eine andere Sicht auf C# haben.

Chris Marinos ist Softwareberater und Microsoft MVP mit dem Schwerpunkt auf F#. Er arbeitet für SRT Solutions in Ann Arbor, Michigan, und ist von F# und der funktionalen Programmierung begeistert. Sie können seine Vorträge zu diesen und zu anderen interessanten Themen bei Veranstaltungen in der Ann Arbor-Region hören oder seinen Blog unter chrismarinos.com lesen.

Unser Dank gilt den folgenden technischen Experten für das Lektorat dieses Artikels: Cameron Frederick und Daniel Mohl.