Difference between class level self-identifier and user level in F #?

Is there any semantic difference between class level and member self identifiers in F #? For example, consider this class:

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 

Compared to this class:

 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 

The only difference is that the PrintMessage implementation refers to this in one or self in another. Is there any difference in semantics? If not, is there a stylistic reason to prefer each other?

+7
f #
source share
2 answers

There is no real semantic difference between the two. As a rule, I suggest moving on to your first example - prefer an identifier that is closer in scope, it simplifies reading and refactoring code later. As a side note, people usually use this for both class identifiers and member level, in which case one of the member level levels loses the class level.

In such scenarios, it is useful to look at the compiled code in a disassembler such as ILSpy. If you do this, you will find that the only difference is the extra zero check that is inserted in the self.Data case.

On the other hand, there is a difference between a class that uses the class level identifier and one that does not (a series of initialization checks are inserted into all members of the class). It is better to avoid them if possible, and your example may be rewritten so as not to require it.

+5
source share

As mentioned in scrwtp, this seems to be a commonly used identifier, and this is my preference. Another very common is x . I usually use the class level identifier when it has been used several times throughout the class and, of course, when it was used in the constructor. And in these cases, I would use __ (two underscores) as a member level identifier to indicate that the value is being ignored. You cannot use _ and actually ignore it, since it is a compilation error, but linting tools often treat __ as the same thing and do not give you a warning about an unused identifier.

When you add an identifier at the class level and do not use it, you get a warning:

A recursive reference to the 'self' object is not used. Having a recursive object reference adds runtime initialization checks to members of this and derived types. Try removing this recursive object reference.

Consider this code:

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

Here's what these classes look like after compilation / decompilation:

 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 init@22 ; public Program.MyClassAsSelf X { get { if ( this.init@22 < 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; this.init@22 = 1; } } 
 public class MyClassAsSelfUnused { internal int init@25-1 ; public Unit X { get { if ( this.init@25-1 < 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; this.init@25-1 = 1; } } 

Note that there is a check that the variable was set in the constructor. If the test fails, the function is called: LanguagePrimitives.IntrinsicFunctions.FailInit() . This is an exception:

System.InvalidOperationException: Initializing an object or value led to recursive access of the object or object until it was fully initialized.

I assume the warning only exists so that you can avoid the slight overhead of unnecessary runtime checking. However, I do not know how to create a situation in which an error occurs, so I do not know the exact purpose of the check. Perhaps someone else can shed light on this?

+4
source share

All Articles