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 ('T
static member get_Zero
istatic 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 systemieIEnumerable<'T>
. - Zmiana
member this.Sum
namember 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.