Udostępnij za pośrednictwem


Rzutowanie i konwersje (F#)

W tym artykule opisano obsługę konwersji typów w języku F#.

Typy arytmetyczne

Język F# udostępnia operatory konwersji dla konwersji arytmetycznych między różnymi typami pierwotnymi, takimi jak między typami liczb całkowitych i zmiennoprzecinkowych. Operatory konwersji całkowitej i znakowej sprawdziły i nie zaznaczono formularzy; operatory zmiennoprzecinkowe i enum operator konwersji nie. Nieznaczone formularze są zdefiniowane w pliku FSharp.Core.Operators , a zaznaczone formularze są zdefiniowane w pliku FSharp.Core.Operators.Checked. Sprawdzone formularze sprawdzają przepełnienie i generują wyjątek środowiska uruchomieniowego, jeśli wynikowa wartość przekracza limity typu docelowego.

Każdy z tych operatorów ma taką samą nazwę jak nazwa typu docelowego. Na przykład w poniższym kodzie, w którym typy są jawnie oznaczone adnotacjami, byte pojawia się z dwoma różnymi znaczeniami. Pierwsze wystąpienie to typ, a drugi to operator konwersji.

let x : int = 5

let b : byte = byte x

W poniższej tabeli przedstawiono operatory konwersji zdefiniowane w języku F#.

Operator opis
byte Przekonwertuj na bajt , 8-bitowy niepodpisany typ.
sbyte Przekonwertuj na podpisany bajt.
int16 Przekonwertuj na 16-bitową liczbę całkowitą ze znakiem.
uint16 Przekonwertuj na 16-bitową niepodpisaną liczbę całkowitą.
int32, int Przekonwertuj na 32-bitową liczbę całkowitą ze znakiem.
uint32 Przekonwertuj na 32-bitową niepodpisaną liczbę całkowitą.
int64 Przekonwertuj na 64-bitową liczbę całkowitą ze znakiem.
uint64 Przekonwertuj na 64-bitową niepodpisaną liczbę całkowitą.
nativeint Przekonwertuj na natywną liczbę całkowitą.
unativeint Przekonwertuj na niepodpisaną natywną liczbę całkowitą.
float, double Przekonwertuj na 64-bitową liczbę zmiennoprzecinkową IEEE o podwójnej precyzji.
float32, single Przekonwertuj na 32-bitową liczbę zmiennoprzecinkową IEEE o pojedynczej precyzji.
decimal Przekonwertuj na System.Decimal.
char Przekonwertuj na System.Charznak Unicode.
enum Przekonwertuj na typ wyliczony.

Oprócz wbudowanych typów pierwotnych można używać tych operatorów z typami, które implementują op_Explicit metody lub op_Implicit z odpowiednimi podpisami. Na przykład int operator konwersji współpracuje z dowolnym typem, który udostępnia metodę op_Explicit statyczną, która przyjmuje typ jako parametr i zwraca wartość int. Jako specjalny wyjątek od ogólnej reguły, że metody nie mogą być przeciążone przez typ zwracany, można to zrobić dla op_Explicit i op_Implicit.

Wyliczone typy

Operator enum jest operatorem ogólnym, który przyjmuje jeden parametr typu, który reprezentuje typ, na który ma enum być konwertowany. Podczas konwersji na typ wyliczony typ, wnioskowanie typu próbuje określić typ enum , na który chcesz przekonwertować. W poniższym przykładzie zmienna col1 nie jest jawnie oznaczona adnotacjami, ale jej typ jest wnioskowany z późniejszego testu równości. W związku z tym kompilator może wyliczyć, że jest konwertowany na Color wyliczenie. Alternatywnie możesz podać adnotację typu, tak jak col2 w poniższym przykładzie.

type Color =
    | Red = 1
    | Green = 2
    | Blue = 3

// The target type of the conversion cannot be determined by type inference, so the type parameter must be explicit.
let col1 = enum<Color> 1

// The target type is supplied by a type annotation.
let col2 : Color = enum 2

Można również jawnie określić docelowy typ wyliczenia jako parametr typu, jak w poniższym kodzie:

let col3 = enum<Color> 3

Należy pamiętać, że rzutowanie wyliczania działa tylko wtedy, gdy podstawowy typ wyliczenia jest zgodny z konwertowanym typem. W poniższym kodzie nie można skompilować konwersji z powodu niezgodności między elementami int32 i uint32.

// Error: types are incompatible
let col4 : Color = enum 2u

Aby uzyskać więcej informacji, zobacz Wyliczenia.

Rzutowanie typów obiektów

Konwersja między typami w hierarchii obiektów jest podstawowa dla programowania obiektowego. Istnieją dwa podstawowe typy konwersji: rzutowanie w górę (upcasting) i odrzucenie (downcasting). Rzutowanie hierarchii oznacza rzutowanie z pochodnego odwołania do obiektu podstawowego. Takie rzutowanie gwarantuje pracę tak długo, jak klasa bazowa znajduje się w hierarchii dziedziczenia klasy pochodnej. Rzutowanie hierarchii z odwołania do obiektu podstawowego do pochodnego odwołania do obiektu kończy się powodzeniem tylko wtedy, gdy obiekt rzeczywiście jest wystąpieniem poprawnego typu docelowego (pochodnego) lub typu pochodzącego z typu docelowego.

Język F# udostępnia operatory dla tych typów konwersji. Operator :> rzutuje hierarchię, a :?> operator rzutuje hierarchię.

Emisja upcasting

W wielu językach obiektowych emisja upcasting jest niejawna; w języku F# reguły są nieco inne. Emisja upcasting jest stosowana automatycznie po przekazaniu argumentów do metod w typie obiektu. Jednak w przypadku funkcji let-bound w module emisja upcasting nie jest automatyczna, chyba że typ parametru jest zadeklarowany jako typ elastyczny. Aby uzyskać więcej informacji, zobacz Typy elastyczne.

Operator :> wykonuje rzutowanie statyczne, co oznacza, że powodzenie rzutowania jest określane w czasie kompilacji. Jeśli rzutowanie używające :> kompiluje się pomyślnie, jest to prawidłowe rzutowanie i nie ma szans na awarię w czasie wykonywania.

Możesz również użyć upcast operatora , aby wykonać taką konwersję. Następujące wyrażenie określa konwersję w górę hierarchii:

upcast expression

Gdy używasz operatora upcast, kompilator próbuje wywnioskować typ, na który jest konwertowany z kontekstu. Jeśli kompilator nie może określić typu docelowego, kompilator zgłasza błąd. Adnotacja typu może być wymagana.

Obniżanie emisji

Operator :?> wykonuje rzutowanie dynamiczne, co oznacza, że powodzenie rzutowania jest określane w czasie wykonywania. Rzutowanie :?> używające operatora nie jest sprawdzane w czasie kompilacji, ale w czasie wykonywania podejmowana jest próba rzutowania do określonego typu. Jeśli obiekt jest zgodny z typem docelowym, rzutowanie powiedzie się. Jeśli obiekt nie jest zgodny z typem docelowym, środowisko uruchomieniowe zgłasza błąd InvalidCastException.

Możesz również użyć downcast operatora do przeprowadzenia konwersji typu dynamicznego. Następujące wyrażenie określa konwersję w dół hierarchii na typ, który jest wnioskowany z kontekstu programu:

downcast expression

Jeśli w przypadku upcast operatora kompilator nie może wywnioskować określonego typu docelowego z kontekstu, zgłasza błąd. Adnotacja typu może być wymagana.

Poniższy kod ilustruje użycie operatorów :> i :?> . Kod ilustruje, że :?> operator jest najlepiej używany, gdy wiadomo, że konwersja powiedzie się, ponieważ zgłasza InvalidCastException błąd w przypadku niepowodzenia konwersji. Jeśli nie wiesz, że konwersja powiedzie się, test typu używający match wyrażenia jest lepszy, ponieważ pozwala uniknąć obciążenia związanego z generowaniem wyjątku.

type Base1() =
    abstract member F : unit -> unit
    default u.F() =
     printfn "F Base1"

type Derived1() =
    inherit Base1()
    override u.F() =
      printfn "F Derived1"


let d1 : Derived1 = Derived1()

// Upcast to Base1.
let base1 = d1 :> Base1

// This might throw an exception, unless
// you are sure that base1 is really a Derived1 object, as
// is the case here.
let derived1 = base1 :?> Derived1

// If you cannot be sure that b1 is a Derived1 object,
// use a type test, as follows:
let downcastBase1 (b1 : Base1) =
   match b1 with
   | :? Derived1 as derived1 -> derived1.F()
   | _ -> ()

downcastBase1 base1

Ponieważ operatory downcast ogólne i upcast polegają na wnioskowaniu typu w celu określenia argumentu i zwracanego typu, możesz zastąpić let base1 = d1 :> Base1 w poprzednim przykładzie kodu ciągiem let base1: Base1 = upcast d1.

Wymagana jest adnotacja typu, ponieważ upcast sama w sobie nie może określić klasy bazowej.

Niejawne konwersje emisji upcast

Niejawne emisje upcast są wstawiane w następujących sytuacjach:

  • W przypadku podawania parametru funkcji lub metody o znanym typie nazwanym. Obejmuje to, gdy konstrukcja, taka jak wyrażenia obliczeniowe lub fragmentowanie, staje się wywołaniem metody.

  • Podczas przypisywania do lub mutowania pola rekordu lub właściwości, która ma znany typ nazwany.

  • Gdy gałąź if/then/else wyrażenia lub match ma znany typ docelowy pochodzący z innej gałęzi lub ogólnego znanego typu.

  • Gdy element wyrażenia listy, tablicy lub sekwencji ma znany typ docelowy.

Rozważmy na przykład następujący kod:

open System
open System.IO

let findInputSource () : TextReader =
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt")

W tym miejscu gałęzie warunkowego obliczenia a TextReader i StreamReader . W drugiej gałęzi znany typ docelowy pochodzi TextReader z adnotacji typu w metodzie i od pierwszej gałęzi. Oznacza to, że w drugiej gałęzi nie jest wymagana emisja upcast.

Aby wyświetlić ostrzeżenie w każdym momencie jest używana dodatkowa niejawna emisja upcast, można włączyć ostrzeżenie 3388 (/warnon:3388 lub właściwość <WarnOn>3388</WarnOn>).

Niejawne konwersje liczbowe

Język F# używa jawnego rozszerzania typów liczbowych w większości przypadków za pośrednictwem operatorów konwersji. Na przykład jawne rozszerzanie jest wymagane w przypadku większości typów liczbowych, takich jak int8 lub int16, lub z float32 do float64, albo, gdy typ źródłowy lub docelowy jest nieznany.

Jednak niejawne rozszerzanie jest dozwolone dla 32-bitowych liczb całkowitych rozszerzonych do 64-bitowych liczb całkowitych, w takich samych sytuacjach jak niejawne emisje upcast. Rozważmy na przykład typowy kształt interfejsu API:

type Tensor(…) =
    static member Create(sizes: seq<int64>) = Tensor(…)

Można użyć literałów liczb całkowitych dla int64:

Tensor.Create([100L; 10L; 10L])

Lub literały liczb całkowitych dla int32:

Tensor.Create([int64 100; int64 10; int64 10])

Rozszerzanie odbywa się automatycznie dla int32 elementu do int64, int32 do nativeinti int32 do double, gdy zarówno typ źródłowy, jak i docelowy są znane podczas wnioskowania typu. W takich przypadkach, jak w poprzednich przykładach, int32 można użyć literałów:

Tensor.Create([100; 10; 10])

Opcjonalnie można również włączyć ostrzeżenie 3389 (/warnon:3389 lub właściwość <WarnOn>3389</WarnOn>), aby wyświetlić ostrzeżenie w każdym punkcie jest używane niejawne rozszerzanie liczbowe.

. Konwersje niejawne w stylu NET

Interfejsy API platformy .NET umożliwiają definiowanie op_Implicit metod statycznych w celu zapewnienia niejawnych konwersji między typami. Są one stosowane automatycznie w kodzie języka F# podczas przekazywania argumentów do metod. Rozważmy na przykład następujący kod wykonujący jawne wywołania op_Implicit metod:

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")

. Konwersje stylu op_Implicit net są stosowane automatycznie dla wyrażeń argumentów, gdy typy są dostępne dla wyrażenia źródłowego i typu docelowego:

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")

Opcjonalnie można również włączyć ostrzeżenie 3395 (/warnon:3395 lub właściwość <WarnOn>3395</WarnOn>), aby wyświetlić ostrzeżenie w każdym punkcie . Używana jest niejawna konwersja w stylu NET.

. Konwersje w stylu op_Implicit NET są również stosowane automatycznie dla wyrażeń innych niż metoda-argument w takich samych sytuacjach jak niejawne emisje upcast. Jednak w przypadku powszechnie lub niewłaściwie używanych niejawnych konwersji mogą źle współdziałać z wnioskowaniem typu i prowadzić do kodu, który jest trudniejszy do zrozumienia. Z tego powodu te zawsze generują ostrzeżenia, gdy są używane w pozycjach innych niż argumenty.

Aby wyświetlić ostrzeżenie w każdym momencie, że element . Niejawna konwersja typu NET jest używana dla argumentu innego niż metoda. Można włączyć ostrzeżenie 3391 (/warnon:3391 lub właściwość <WarnOn>3391</WarnOn>).

W przypadku użycia niejawnych konwersji podano następujące opcjonalne ostrzeżenia:

  • /warnon:3388 (dodatkowa niejawna emisja upcast)
  • /warnon:3389 (niejawne rozszerzanie liczbowe)
  • /warnon:3391 (op_Implicit domyślnie w argumentach innych niż metoda)
  • /warnon:3395 (op_Implicit w argumentach metody)

Zobacz też