Rozszerzenia typów

Rozszerzenia typów (nazywane również rozszerzeniami) to rodzina funkcji, które umożliwiają dodawanie nowych elementów członkowskich do wcześniej zdefiniowanego typu obiektu. Trzy funkcje to:

  • Rozszerzenia typu wewnętrznego
  • Rozszerzenia typów opcjonalnych
  • Metody rozszerzeń

Każdy może być używany w różnych scenariuszach i ma różne kompromisy.

Składnia

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

Rozszerzenia typu wewnętrznego

Rozszerzenie typu wewnętrznego to rozszerzenie typu, które rozszerza typ zdefiniowany przez użytkownika.

Rozszerzenia typu wewnętrznego muszą być zdefiniowane w tym samym pliku i w tej samej przestrzeni nazw lub module co typ, który rozszerzają. Każda inna definicja spowoduje, że będą one opcjonalnymi rozszerzeniami typów.

Rozszerzenia typu wewnętrznego są czasami czystszym sposobem oddzielenia funkcji od deklaracji typu. W poniższym przykładzie pokazano, jak zdefiniować rozszerzenie typu wewnętrznego:

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

Użycie rozszerzenia typu umożliwia oddzielenie każdego z następujących elementów:

  • Deklaracja Variant typu
  • Funkcjonalność drukowania Variant klasy w zależności od jej "kształtu"
  • Sposób uzyskiwania dostępu do funkcji drukowania za pomocą notacji w stylu .obiektu

Jest to alternatywa dla zdefiniowania wszystkiego jako elementu członkowskiego w systemie Variant. Chociaż nie jest to z natury lepsze podejście, może to być czystsza reprezentacja funkcji w niektórych sytuacjach.

Rozszerzenia typu wewnętrznego są kompilowane jako elementy członkowskie typu, które rozszerzają, i pojawiają się w typie, gdy typ jest badany przez odbicie.

Rozszerzenia typów opcjonalnych

Opcjonalne rozszerzenie typu to rozszerzenie, które pojawia się poza oryginalnym modułem, przestrzenią nazw lub zestawem rozszerzanego typu.

Opcjonalne rozszerzenia typów są przydatne do rozszerzania typu, którego nie zdefiniowano samodzielnie. Na przykład:

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
        }

Teraz możesz uzyskać dostęp RepeatElements tak, jakby był członkiem IEnumerable<T> , o ile Extensions moduł jest otwarty w zakresie, w którym pracujesz.

Opcjonalne rozszerzenia nie są wyświetlane w typie rozszerzonym podczas badania przez odbicie. Opcjonalne rozszerzenia muszą znajdować się w modułach i są w zakresie tylko wtedy, gdy moduł zawierający rozszerzenie jest otwarty lub znajduje się w innym zakresie.

Opcjonalne elementy członkowskie rozszerzenia są kompilowane do statycznych elementów członkowskich, dla których wystąpienie obiektu jest przekazywane niejawnie jako pierwszy parametr. Działają one jednak tak, jakby były członkami wystąpienia lub statycznymi elementami członkowskimi zgodnie z ich deklarowanym sposobem.

Opcjonalne elementy członkowskie rozszerzenia nie są również widoczne dla użytkowników języka C# lub Visual Basic. Mogą być używane tylko w innym kodzie języka F#.

Ogólne ograniczenie rozszerzeń typów wewnętrznych i opcjonalnych

Można zadeklarować rozszerzenie typu w typie ogólnym, w którym jest ograniczona zmienna typu. Wymaganie polega na tym, że ograniczenie deklaracji rozszerzenia jest zgodne z ograniczeniem zadeklarowanego typu.

Jednak nawet jeśli ograniczenia są dopasowywane między zadeklarowanym typem a rozszerzeniem typu, możliwe jest, aby ograniczenie zostało wywnioskowane przez treść rozszerzonego elementu członkowskiego, które nakłada inne wymaganie dla parametru typu niż zadeklarowany typ. Na przykład:

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

Nie ma możliwości, aby ten kod działał z opcjonalnym rozszerzeniem typu:

  • Tak jak to jest, element członkowski Sum ma inne ograniczenie ( 'Tstatic member get_Zero i static member (+)) niż definiuje rozszerzenie typu.
  • Zmodyfikowanie rozszerzenia typu tak, aby miało to samo ograniczenie, co Sum nie będzie już zgodne ze zdefiniowanym ograniczeniem w systemie IEnumerable<'T>.
  • Zmiana member this.Sum na member inline this.Sum spowoduje wystąpienie błędu niezgodności ograniczeń typu.

Pożądane są metody statyczne, które "unoszą się w przestrzeni" i mogą być prezentowane tak, jakby rozszerzały typ. Jest to miejsce, w którym metody rozszerzeń stają się niezbędne.

Metody rozszerzeń

Na koniec metody rozszerzeń (czasami nazywane "składowymi rozszerzenia stylu C#") można zadeklarować w języku F# jako statyczną metodę składową w klasie.

Metody rozszerzeń są przydatne w przypadku definiowania rozszerzeń w typie ogólnym, który ogranicza zmienną typu. Na przykład:

namespace Extensions

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

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

W przypadku użycia ten kod będzie wyświetlany tak, jakby Sum był zdefiniowany na IEnumerable<T>, tak długo, jak Extensions został otwarty lub znajduje się w zakresie.

Aby rozszerzenie było dostępne dla VB.NET kodu, na poziomie zestawu jest wymagany dodatkowy ExtensionAttribute :

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

Inne uwagi

Rozszerzenia typów mają również następujące atrybuty:

  • Można rozszerzyć dowolny typ, do którego można uzyskać dostęp.
  • Rozszerzenia typu wewnętrznego i opcjonalnego mogą definiować dowolny typ elementu członkowskiego, a nie tylko metody. Więc właściwości rozszerzenia są również możliwe, na przykład.
  • Token self-identifier w składni reprezentuje wystąpienie wywoływanego typu, podobnie jak zwykłe elementy członkowskie.
  • Rozszerzone elementy członkowskie mogą być statyczne lub składowe wystąpień.
  • Zmienne typu w rozszerzeniu typu muszą być zgodne z ograniczeniami zadeklarowanego typu.

Istnieją również następujące ograniczenia dotyczące rozszerzeń typów:

  • Rozszerzenia typów nie obsługują metod wirtualnych ani abstrakcyjnych.
  • Rozszerzenia typów nie obsługują metod zastępowania jako rozszerzeń.
  • Rozszerzenia typów nie obsługują statycznie rozwiązanych parametrów typu.
  • Opcjonalne rozszerzenia typu nie obsługują konstruktorów jako rozszerzeń.
  • Nie można zdefiniować rozszerzeń typów na skrótach typów.
  • Rozszerzenia typów są nieprawidłowe byref<'T> (choć można je zadeklarować).
  • Rozszerzenia typów są nieprawidłowe dla atrybutów (choć można je zadeklarować).
  • Można zdefiniować rozszerzenia, które przeciążą inne metody o tej samej nazwie, ale kompilator języka F# daje preferencje metod innych niż rozszerzenia, jeśli istnieje niejednoznaczne wywołanie.

Na koniec, jeśli istnieje wiele rozszerzeń typu wewnętrznego dla jednego typu, wszystkie elementy członkowskie muszą być unikatowe. W przypadku rozszerzeń typów opcjonalnych elementy członkowskie w różnych rozszerzeniach typów do tego samego typu mogą mieć takie same nazwy. Błędy niejednoznaczności występują tylko wtedy, gdy kod klienta otwiera dwa różne zakresy, które definiują te same nazwy elementów członkowskich.

Zobacz też