Teilen über


Typerweiterungen

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

  • Systeminterne Typerweiterungen
  • Optionale Typerweiterungen
  • Erweiterungsmethoden

Jede kann in verschiedenen Szenarien verwendet werden und hat unterschiedliche Kompromisse.

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
    ...

Systeminterne Typerweiterungen

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

Systeminterne 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.

Systeminterne Typerweiterungen sind manchmal eine übersichtlichere Möglichkeit, Funktionen von der Typdeklaration zu trennen. Das folgende Beispiel zeigt, wie eine systeminterne 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

Durch die Verwendung einer Typerweiterung können Sie jede der folgenden Elemente trennen:

  • Die Deklaration eines Variant Typs
  • Funktionalität zum Drucken der Variant Klasse je nach "Shape"
  • Eine Möglichkeit für den Zugriff auf die Druckfunktionalität mit Objektformat .-notation

Dies ist eine Alternative zum Definieren aller Elemente als Mitglied Variant. Obwohl es sich nicht um einen inhärenten besseren Ansatz handelt, kann es in einigen Situationen eine übersichtlichere Darstellung der Funktionalität sein.

Systeminterne Typerweiterungen werden als Member des Typs kompiliert, den sie erweitern, und werden auf dem Typ angezeigt, wenn der Typ durch Spiegelung untersucht wird.

Optionale Typerweiterungen

Eine optionale Typerweiterung ist eine Erweiterung, die außerhalb des ursprünglichen Moduls, Namespaces oder der Assembly des Typs angezeigt wird, der erweitert wird.

Optionale Typerweiterungen sind nützlich, um einen Typ zu erweitern, den Sie selbst nicht 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 jetzt darauf zugreifen RepeatElements , als ob es sich um ein Mitglied handelt IEnumerable<T> , solange das Extensions Modul im Bereich geöffnet wird, in dem Sie arbeiten.

Optionale Erweiterungen werden beim Untersuchen durch Spiegelung nicht für den erweiterten Typ angezeigt. Optionale Erweiterungen müssen sich in Modulen befinden, und sie befinden sich nur im Bereich, wenn das Modul, das die Erweiterung enthält, geöffnet ist oder sich anderweitig im Bereich befindet.

Optionale Erweiterungsmember werden zu statischen Membern kompiliert, für die die Objektinstanz implizit als erster Parameter übergeben wird. Sie verhalten sich jedoch so, als ob sie Instanzmitglieder oder statische Member sind, je nach ihrer Deklaration.

Optionale Erweiterungsmember sind auch für C#- oder Visual Basic-Consumer nicht sichtbar. Sie können nur im anderen F#-Code verwendet werden.

Generische Einschränkung systeminterner und optionaler Typerweiterungen

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

Auch wenn Einschränkungen zwischen einem deklarierten Typ und einer Typerweiterung abgeglichen werden, ist es möglich, dass eine Einschränkung vom Textkörper eines erweiterten Elements abgeleitet wird, das eine andere Anforderung für den Typparameter als den deklarierten Typ angibt. 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

Dieser Code kann nicht mit einer optionalen Typerweiterung verwendet werden:

  • Wie das ist, hat das Sum Element eine andere Einschränkung für 'T (static member get_Zero und static member (+)) als das, was die Typerweiterung definiert.
  • Wenn Sie die Typerweiterung so ändern, dass sie dieselbe Einschränkung aufweist, wie Sum sie nicht mehr mit der definierten Einschränkung IEnumerable<'T>übereinstimmt.
  • member inline this.Sum Wenn sie member this.Sum geändert werden, tritt ein Fehler auf, bei dem Typeneinschränkungen nicht übereinstimmen.

Was gewünscht ist, sind statische Methoden, die "im Raum schweben" und so dargestellt werden können, als ob sie einen Typ erweitern. Hier werden Erweiterungsmethoden benötigt.

Erweiterungsmethoden

Schließlich können Erweiterungsmethoden (manchmal als "C#-Stilerweiterungsmember" 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, der die Typvariable einschränkt. 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, wird er so angezeigt, als ob Sum er definiert IEnumerable<T>ist, solange Extensions er geöffnet wurde 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 haben auch die folgenden Attribute:

  • Jeder Typ, auf den zugegriffen werden kann, kann erweitert werden.
  • Systeminterne und optionale Typerweiterungen können jeden Membertyp definieren, nicht nur Methoden. Erweiterungseigenschaften sind also auch möglich, z. B.
  • Das self-identifier Token in der Syntax stellt die Instanz des typs dar, der aufgerufen wird, genau wie normale Member.
  • Erweiterte Member können statisch oder Instanzmber 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 Außerkraftsetzungsmethoden als Augmentations.
  • Typerweiterungen unterstützen nicht statisch aufgelöste Typparameter.
  • Optionale Typerweiterungen unterstützen Konstruktoren nicht als Erweiterung.
  • Typerweiterungen können nicht für Typkürzel definiert werden.
  • Typerweiterungen sind ungültig für byref<'T> (obwohl sie deklariert werden können).
  • Typerweiterungen sind für Attribute nicht gültig (obwohl sie deklariert werden können).
  • Sie können Erweiterungen definieren, die andere Methoden desselben Namens überladen, aber der F#-Compiler bevorzugt Nichterweiterungsmethoden, wenn ein mehrdeutiger Aufruf vorhanden ist.

Wenn mehrere systeminterne Typenerweiterungen für einen Typ vorhanden sind, müssen alle Member eindeutig sein. Bei optionalen Typerweiterungen können Elemente in unterschiedlichen Typerweiterungen mit demselben Typ dieselben Namen haben. Mehrdeutigkeitsfehler treten nur auf, wenn Clientcode zwei verschiedene Bereiche öffnet, die dieselben Membernamen definieren.

Siehe auch