2017-06-20 13 views
7

Czy jest jakaś semantyczna różnica między identyfikatorami na poziomie klasy i na poziomie członków w F #? Na przykład, rozważmy tę klasę:Różnica między identyfikatorem własnym na poziomie klasy i na poziomie członkowskim w F #?

type MyClass2(dataIn) as self = 
    let data = dataIn 
    do 
     self.PrintMessage() 
    member this.Data = data 
    member this.PrintMessage() = 
     printfn "Creating MyClass2 with Data %d" this.Data 

Versus tej klasie:

type MyClass2(dataIn) as self = 
    let data = dataIn 
    do 
     self.PrintMessage() 
    member this.Data = data 
    member this.PrintMessage() = 
     printfn "Creating MyClass2 with Data %d" self.Data 

Jedyną różnicą jest to, że realizacja PrintMessage odwołuje this w jednym vs. self w drugiej. Czy jest jakaś różnica w semantykach? Jeśli nie, czy istnieje stylistyczny powód, aby preferować jeden nad drugim?

Odpowiedz

5

Nie ma prawdziwej semantycznej różnicy między tymi dwoma. Zasadniczo sugeruję, abyś podał swój pierwszy przykład - preferujesz identyfikator, który jest bliżej zakresu, ułatwia czytanie i refaktoryzację kodu później. Na marginesie, ludzie zwykle używają this zarówno dla identyfikatorów na poziomie klasy, jak i na poziomie członków, w tym przypadku na poziomie klasy jeden cienie na poziomie klasy jeden.

W tego rodzaju scenariuszach warto przyjrzeć się skompilowanemu kodowi w disasemsemrze, takim jak ILSpy. Jeśli to zrobisz, okaże się, że jedyną różnicą jest dodatkowa kontrola zerowa, która jest wstawiana w przypadku self.Data.

Z drugiej strony istnieje różnica między klasą, która korzysta z identyfikatora na poziomie klasy, a inną, która nie (seryjne kontrole są wstawiane do wszystkich członków klasy). Najlepiej unikać ich, jeśli to możliwe, a twój przykład można przepisać, aby tego nie wymagać.

4

Jak wspomniano przez scrwtp, this wydaje się być powszechnie używanym identyfikatorem i jest to moje preferencje. Innym bardzo powszechnym jest x. Zwykle używam identyfikatora na poziomie klasy, gdy jest on używany wielokrotnie w klasie i oczywiście, gdy jest używany w konstruktorze. I w tych przypadkach użyłbym __ (dwa podkreślenia) jako identyfikator poziomu elementu, aby zaznaczyć, że wartość jest ignorowana. Nie można użyć numeru _ i zignorować go, ponieważ jest to błąd kompilacji, ale narzędzia do linkowania często będą uważać, że to __, i uniknąć ostrzeżenia o nieużywanym identyfikatorze.

Po dodaniu identyfikatora poziomu klasy i nie używać go można dostać ostrzeżenie:

Rekurencyjne obiekt odniesienia „ja” jest nieużywany. Obecność referencji obiektu rekursywnego dodaje kontrole inicjowania w czasie wykonywania do elementów w tym i wyprowadzonych typach. Rozważ usunięcie tego rekurencyjnego odwołania do obiektu.

Rozważmy następujący kod:

type MyClass() = 
    member self.X = self 

type MyClassAsSelf() as self = 
    member __.X = self 

type MyClassAsSelfUnused() as self = // <-- warning here 
    member __.X =() 

To właśnie te zajęcia wyglądają jak po kompilacji/dekompilacji:

public class MyClass 
{ 
    public Program.MyClass X 
    { 
     get 
     { 
      return this; 
     } 
    } 

    public MyClass() : this() 
    { 
    } 
} 
public class MyClassAsSelf 
{ 
    internal FSharpRef<Program.MyClassAsSelf> self = new FSharpRef<Program.MyClassAsSelf>(null); 

    internal int [email protected]; 

    public Program.MyClassAsSelf X 
    { 
     get 
     { 
      if ([email protected] < 1) 
      { 
       LanguagePrimitives.IntrinsicFunctions.FailInit(); 
      } 
      return LanguagePrimitives.IntrinsicFunctions.CheckThis<Program.MyClassAsSelf>(this.self.contents); 
     } 
    } 

    public MyClassAsSelf() 
    { 
     FSharpRef<Program.MyClassAsSelf> self = this.self; 
     this..ctor(); 
     this.self.contents = this; 
     [email protected] = 1; 
    } 
} 
public class MyClassAsSelfUnused 
{ 
    internal int [email protected]; 

    public Unit X 
    { 
     get 
     { 
      if ([email protected] < 1) 
      { 
       LanguagePrimitives.IntrinsicFunctions.FailInit(); 
      } 
     } 
    } 

    public MyClassAsSelfUnused() 
    { 
     FSharpRef<Program.MyClassAsSelfUnused> self = new FSharpRef<Program.MyClassAsSelfUnused>(null); 
     FSharpRef<Program.MyClassAsSelfUnused> self2 = self2; 
     this..ctor(); 
     self.contents = this; 
     [email protected] = 1; 
    } 
} 

Należy zauważyć, że istnieje kontrola, czy zmienna została ustawiona w konstruktorze. Jeśli kontrola nie powiedzie się, wywoływana jest funkcja: LanguagePrimitives.IntrinsicFunctions.FailInit().Jest to wyjątek zgłoszony:

System.InvalidOperationException: Inicjalizacja obiektu lub wartości spowodowała, że ​​obiekt lub wartość były dostępne rekursywnie przed całkowitym zainicjowaniem.

Domyślam się, że ostrzeżenie jest po to, aby uniknąć lekkiego niepotrzebnego czeku. Jednak nie wiem, jak skonstruować sytuację, w której błąd jest generowany, więc nie znam dokładnego celu tej kontroli. Być może ktoś inny może rzucić światło na to?

+1

Jest to istotne, gdy dziedziczenie pojawia się w obrazie - pamiętam, że widziałem to w wyjątkowo owłosionym scenariuszu z podstawową klasą abstrakcyjną z własnym identyfikatorem. Ale nie mam tu żadnego przytoczonego z zewnątrz, samodzielnego przykładu. – scrwtp

Powiązane problemy