【问题标题】:Difference between class-level and member-level self-identifier in F#?F# 中的类级和成员级自标识符之间的区别?
【发布时间】:2017-06-21 18:51:47
【问题描述】:

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

与此类:

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

唯一的区别是 PrintMessage 的实现在一个中引用 this 而在另一个中引用 self。语义上有什么区别吗?如果不是,是否有风格上的理由偏爱其中一个?

【问题讨论】:

    标签: f#


    【解决方案1】:

    两者之间没有真正的语义差异。根据经验,我建议使用您的第一个示例 - 更喜欢范围更近的标识符,它使以后更容易阅读和重构代码。附带说明一下,人们通常将this 用于类和成员级标识符,在这种情况下,成员级 1 会影响类级 1。

    在这种情况下,在 ILSpy 等反汇编程序中查看编译后的代码很有用。如果你这样做,你会发现唯一的区别是在self.Data case 中插入了一个额外的空检查。

    另一方面,使用类级别标识符的类和不使用类级别标识符的类之间存在差异(一系列初始化检查被插入到所有类成员中)。如果可能,最好避免使用它们,并且您的示例可以重写为不需要它们。

    【讨论】:

      【解决方案2】:

      正如 scrwtp 所提到的,this 似乎是一个常用的标识符,这是我的偏好。另一个很常见的是x。当它在整个类中多次使用时,当然还有在构造函数中使用时,我倾向于使用类级标识符。在这些情况下,我将使用__(两个下划线)作为成员级别标识符,以表示该值被忽略。您不能使用 _ 并实际上忽略它,因为它是一个编译错误,但 linting 工具通常会将 __ 视为同一事物,并避免向您发出有关未使用标识符的警告。

      当您添加类级别标识符但不使用它时,您会收到警告:

      递归对象引用“self”未使用。递归对象引用的存在向 this 和派生类型中的成员添加了运行时初始化检查。考虑删除此递归对象引用。

      考虑这段代码:

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

      这是这些类在编译/反编译后的样子:

      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;
          }
      }
      

      请注意,检查是否已在构造函数中设置了变量。如果检查失败,则调用一个函数:LanguagePrimitives.IntrinsicFunctions.FailInit()。这是抛出的异常:

      System.InvalidOperationException:对象或值的初始化导致对象或值在完全初始化之前被递归访问。

      我猜这个警告只是为了避免不必要的运行时检查带来的轻微开销。但是,我不知道如何构造一个抛出错误的情况,所以我不知道检查的确切目的。也许其他人可以阐明这一点?

      【讨论】:

      • 当继承进入图片时它变得相关 - 我记得在一个特别多毛的场景中看到它具有一个具有自我标识符的基本抽象类。但我没有一个临时的、独立的例子可以在这里给出。
      猜你喜欢
      • 2015-11-03
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 2010-12-28
      • 1970-01-01
      • 1970-01-01
      • 2014-01-20
      相关资源
      最近更新 更多