2012-07-27 13 views
21

Dla przykładowego programu:Rodzaj nie mają zerowy jako właściwą wartość

type public MyClass(reasonForLiving:string) = 
    member x.ReasonForLiving with get() = reasonForLiving 

let classFactory() = MyClass("up to you") 
let live() = 
    let instance = classFactory() 
    if instance = null then raise(System.Exception("null is not living... that's why OO languages die from bugs")) 
    instance 

pojawia się błąd „typu«MojaKlasa»nie mają zerowy jako właściwą wartość” Kiedy idę do korzystania z tej klasy jako wartość zwracana niejawnie wpisanych funkcji i porównaj ją z null (b/c wymagań zgodności z wtryskiem zależności C# nie mogę polegać na typach opcji F #).

mogę łatwo rozwiązać ten problem poprzez zmianę czek wartości null do:

if instance :> obj = null then 

Jednak wiem („czuć”) jest to całkowicie „źle”. Zwłaszcza, gdy zastanawiam się, w jaki sposób MyClass jest typem odniesienia, który nie powinien być zapakowany (mówiąc z tła C#).

Czytałem o "Ograniczeniu wartości F #" i jego wpływie na wnioskowanie o typie, ale nie mogę wyobrazić sobie, jak to dotyczy tego scenariusza.

P: Czy jest inny sposób na zrobienie tego?

Oprócz # 1: znalazłem prostszy sposób uzyskiwanie błąd ...

type public MyClass(reasonForLiving:string) = 
    member x.ReasonForLiving with get() = reasonForLiving 
let nullMyClass : MyClass = null 

Poza # 2: Próbowałem System.Nullable bez zastanowienia ... MojaKlasa jest typ odniesienia, a nie typ wartości (struct) wymagany przez Nullable < _>. Tak więc, tylko upewniam mnie, że NAPRAWDĘ mam do czynienia z typem referencyjnym i sprawia, że ​​zastanawiam się, dlaczego obiekt nagle rzuca się w oczy.

Aktualizacja: Dla wszystkich zainteresowanych użyłem tego jako jednego rozwiązania dla wspólnego serwisu usługowego z trzema funkcjami poniżej. Każdy wniosek musi obsługiwać NULL, więc jeśli klasa usług jest określona w F # usługi, trzeba by dodać [<AllowNullLiteral>]:

let private getServiceLocator() = 
    try Some(Microsoft.Practices.ServiceLocation.ServiceLocator.Current) 
    with | _ -> None 

let private getService serviceFactory = 
    let serviceLocator = getServiceLocator() 
    let service = match serviceLocator with 
        | None -> serviceFactory() 
        | _ -> 
        match serviceLocator.Value.GetInstance<'a>() with 
        | null -> serviceFactory() 
        | svc -> svc 
    match service with 
    | null -> None 
    | _ -> Some(service) 

let private getRequiredService serviceFactory = 
    let service = getService serviceFactory 
    match service with 
    | None -> raise(MissingServiceException("")) 
    | _ -> service.Value 

Odpowiedz

39

użyć atrybutu [<AllowNullLiteral>]:

[<AllowNullLiteral>] 
type public MyClass(reasonForLiving:string) = 
    member x.ReasonForLiving with get() = reasonForLiving 

domyślnie F # typy nie dopuść do null (dziękuję niebiosom!). Ten atrybut jest przydatny do współdziałania z innymi językami .NET i umożliwia przypisanie/porównanie z wartością null.

+0

Doh! Po obejrzeniu tego, rozpoznałem go z trzeciego zdania na MSDN "Null Values ​​(F #)": http://msdn.microsoft.com/en-us/library/dd233197(v=vs.110).aspx –

+2

Tylko add - używanie opcji 'Option 't' jest bardziej idiomatyczne, jeśli nie robisz interakcji –

+0

John - Tak, wspomniałem, że nie mogę używać opcji. Tak, prawie każda klasa narażona na C# potrzebuje '[]' do pracy jako programista C#, który oczekiwałby ... :( –

14

Problem z atrybutem AllowNullLiteral jest to, że oprócz co pozwala porównać swoje obiekty na null, ale również umożliwia ustawić swoich obiektów do null.

Zakładając, że nie jest to pożądane dla przypadków użycia, nie jest łatwym alternatywa z nieobserwowalnej wpływu na wydajność:

let inline isNull (x:^T when ^T : not struct) = obj.ReferenceEquals (x, null) 

Wtedy zamiast robić if instance = null then, zamiast robić if isNull instance then.

Będzie to działać dla każdego typu odniesienia (w tym dla rekordów i dalszych użytkowników), ale nie wprowadzi możliwości ustawienia obiektów z typów F # na zero z F # - najlepsze z obu światów.

+3

Kiedyś użyłem tego podejścia do dużego projektu, ale skończyło się na tym, że go żałuję .Jeśli typ może być pusty (albo w F #, albo z powodu interopu), najlepiej, żeby był wyraźny. Dopasowanie, co jest lepsze, zdefiniowanie sprawdzającego null aktywnego wzoru lub 'dopasuj wartość z wartością null -> ...' W końcu takie obejścia okazały się hacky. Dla współdziałania, po prostu dodaj '[]' do twoich typów i objąć nr – Daniel

+0

@Daniel: Dostaję im pression, że OP po prostu chce używać typów F # z C#, więc nie wiem, jak dużo prawdziwego współdziałania się dzieje ... – ildjarn

+0

Powyższa definicja Null spowodowała błąd składni: '' Parametry typu jawnego mogą być używane tylko w module lub wiązania użytkownika'' Próbowałem tego zamiast: '' let isNull (x: obj) = obj.ReferenceEquals (x, Unchecked.defaultof <_>) '' Oczywiście, zaakceptuje dowolną wartość, ponieważ parametr wiążący się automatycznie przerzuć go do obiektu (z następczym wysiłkiem skrzyni). Tak więc ostrzeżenie kompilatora dla wartości niereferencyjnej zostanie utracone. – George

Powiązane problemy