Typerweiterungen

Typerweiterungen (auch als Augmentationen bezeichnet) sind eine Familie von Features, mit denen Sie einem zuvor definierten Objekttyp neue Member hinzufügen können. Die drei Features sind:

  • Intrinsische Typerweiterungen
  • Optionale Typerweiterungen
  • Erweiterungsmethoden

Jede Typerweiterung kann in verschiedenen Szenarien verwendet werden und bietet unterschiedliche Vorteile.

Syntax

// Intrinsic and optional extensions
type typename with
    member self-identifier.member-name =
        body
    ...

// Extension methods
open System.Runtime.CompilerServices

[<Extension>]
type Extensions() =
    [<Extension>]
    static member extension-name (ty: typename, [args]) =
        body
    ...

Intrinsische Typerweiterungen

Eine intrinsische Typerweiterung ist eine Typerweiterung, die einen benutzerdefinierten Typ erweitert.

Intrinsische Typerweiterungen müssen in derselben Datei und im selben Namespace oder Modul wie der Typ definiert werden, den sie erweitern. Jede andere Definition führt dazu, dass sie optionale Typerweiterungen sind.

Intrinsische Typerweiterungen sind manchmal eine bessere Möglichkeit, die Funktionalität von der Typdeklaration zu trennen. Das folgende Beispiel zeigt, wie eine intrinsische Typerweiterung definiert wird:

namespace Example

type Variant =
    | Num of int
    | Str of string
  
module Variant =
    let print v =
        match v with
        | Num n -> printf "Num %d" n
        | Str s -> printf "Str %s" s

// Add a member to Variant as an extension
type Variant with
    member x.Print() = Variant.print x

Mithilfe einer Typerweiterung können Sie die folgenden Elemente voneinander trennen:

  • Die Deklaration eines Variant-Typs
  • Funktionalität zum Ausgeben der Variant-Klasse abhängig von ihrer „Form“
  • Eine Möglichkeit zum Zugreifen auf die Ausgabefunktion mit objektbasierter .-Notation

Dies ist eine Alternative zum Definieren aller Elemente als Member für Variant. Dies ist zwar nicht per se ein besserer Ansatz, kann aber in manchen Situationen eine klarere Darstellung der Funktionalität bieten.

Intrinsische Typerweiterungen werden als Member des Typs kompiliert, den sie erweitern, und werden für den Typ angezeigt, wenn der Typ durch Reflexion untersucht wird.

Optionale Typerweiterungen

Eine optionale Typerweiterung ist eine Erweiterung, die außerhalb des ursprünglichen Moduls, Namespaces oder der Assembly des zu erweiternden Typs vorkommt.

Optionale Typerweiterungen sind nützlich, um einen Typ zu erweitern, den Sie nicht selbst definiert haben. Beispiel:

module Extensions

type IEnumerable<'T> with
    /// Repeat each element of the sequence n times
    member xs.RepeatElements(n: int) =
        seq {
            for x in xs do
                for _ in 1 .. n -> x
        }

Sie können nun so auf RepeatElements zugreifen, als ob es sich um einen Member von IEnumerable<T> handeln würde, solange das Modul Extensions in dem Bereich geöffnet ist, in dem Sie aktuell arbeiten.

Optionale Erweiterungen werden für den erweiterten Typ nicht angezeigt, wenn er durch Reflexion untersucht wird. Optionale Erweiterungen müssen in Modulen enthalten sein, und sie befinden sich nur im Bereich, wenn das Modul, das die Erweiterung enthält, geöffnet ist oder sich auf andere Weise im Bereich befindet.

Member optionaler Erweiterungen werden in statische Member kompiliert, für die die Objektinstanz implizit als erster Parameter übergeben wird. Sie verhalten sich jedoch so, als ob sie Instanzmember oder statische Member wären, entsprechend der Art ihrer Deklarierung.

Optionale Erweiterungselemente sind auch für C#- oder Visual Basic-Consumer nicht sichtbar. Sie können nur in anderem F#-Code genutzt werden.

Generische Einschränkung von intrinsischen und optionalen Typerweiterungen

Es ist möglich, eine Typerweiterung für einen generischen Typ zu deklarieren, bei dem die Typvariable eingeschränkt ist. Voraussetzung ist, dass die Einschränkung der Erweiterungsdeklaration mit der Einschränkung des deklarierten Typs übereinstimmt.

Aber selbst wenn Einschränkungen zwischen einem deklarierten Typ und einer Typerweiterung übereinstimmen, ist es möglich, dass eine Einschränkung aus dem Textkörper eines erweiterten Members abgeleitet wird, die eine andere Anforderung an den Typparameter stellt als der deklarierte Typ. Beispiel:

open System.Collections.Generic

// NOT POSSIBLE AND FAILS TO COMPILE!
//
// The member 'Sum' has a different requirement on 'T than the type IEnumerable<'T>
type IEnumerable<'T> with
    member this.Sum() = Seq.sum this

Es gibt keine Möglichkeit, diesen Code mit einer optionalen Typerweiterung zu verwenden:

  • Somit weist der Sum-Member eine andere Einschränkung für 'T (static member get_Zero und static member (+)) auf als in der Typerweiterung definiert.
  • Wenn die Typerweiterung so geändert wird, dass sie dieselbe Einschränkung wie Sum aufweist, stimmt sie nicht mehr mit der definierten Einschränkung für IEnumerable<'T> überein.
  • Das Ändern von member this.Sum in member inline this.Sum führt zu einem Fehler, der besagt, dass die Typeinschränkungen nicht übereinstimmen.

Gewünscht sind statische Methoden, die „im Raum schweben“ und so dargestellt werden können, als ob sie einen Typ erweitern würden. In diesem Fall werden Erweiterungsmethoden erforderlich.

Erweiterungsmethoden

Schließlich können Erweiterungsmethoden (manchmal auch als Erweiterungsmember im C#-Format bezeichnet) in F# als statische Membermethode für eine Klasse deklariert werden.

Erweiterungsmethoden sind nützlich, wenn Sie Erweiterungen für einen generischen Typ definieren möchten, die die Typvariable einschränken. Beispiel:

namespace Extensions

open System.Collections.Generic
open System.Runtime.CompilerServices

[<Extension>]
type IEnumerableExtensions =
    [<Extension>]
    static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs

Wenn dieser Code verwendet wird, sieht es so aus, als ob Sum für IEnumerable<T> definiert ist, solange Extensions geöffnet ist oder sich im Bereich befindet.

Damit die Erweiterung für VB.NET-Code verfügbar ist, ist auf Assemblyebene ein zusätzliches ExtensionAttribute erforderlich:

module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()

Weitere Hinweise

Typerweiterungen weisen außerdem die folgenden Attribute auf:

  • Jeder Typ, auf den zugegriffen werden kann, kann erweitert werden.
  • Intrinsische und optionale Typerweiterungen können jeden Membertyp definieren, nicht nur Methoden. So sind z. B. auch Erweiterungseigenschaften möglich.
  • Das self-identifier-Token in der Syntax stellt die Instanz des aufgerufenen Typs dar, genau wie normale Member.
  • Erweiterte Member können statische oder Instanzmember sein.
  • Typvariablen für eine Typerweiterung müssen mit den Einschränkungen des deklarierten Typs übereinstimmen.

Die folgenden Einschränkungen gelten auch für Typerweiterungen:

  • Typerweiterungen unterstützen keine virtuellen oder abstrakten Methoden.
  • Typerweiterungen unterstützen keine Überschreibungsmethoden als Augmentationen.
  • Typerweiterungen unterstützen keine statisch aufgelösten Typparameter.
  • Optionale Typerweiterungen unterstützen keine Konstruktoren als Augmentationen.
  • Typerweiterungen können nicht für Typabkürzungen definiert werden.
  • Typerweiterungen sind für byref<'T> ungültig (obwohl sie deklariert werden können).
  • Typerweiterungen sind für Attribute ungültig (obwohl sie deklariert werden können).
  • Sie können Erweiterungen definieren, die andere Methoden mit demselben Namen überladen, aber der F#-Compiler gibt Nicht-Erweiterungsmethoden den Vorzug, wenn ein mehrdeutiger Aufruf vorhanden ist.

Wenn für einen Typ mehrere intrinsische Typerweiterungen vorhanden sind, müssen alle Member eindeutig sein. Bei optionalen Typerweiterungen können Member in unterschiedlichen Typerweiterungen, die auf den gleichen Typ erweitert werden, die gleichen Namen aufweisen. Mehrdeutigkeitsfehler treten nur auf, wenn Clientcode zwei unterschiedliche Gültigkeitsbereiche öffnet, die die gleichen Membernamen definieren.

Weitere Informationen