F # Наследование с несколькими базовыми конструкторами и событиями

#c# #.net #f#

#c# #.net #f#

Вопрос:

В настоящее время существует класс F #, реализованный следующим образом:

 namespace MultiLanguage.FSharpClassLibrary

open MultiLanguage.CSharpClassLibrary
open System
open System.Runtime.Serialization

[<Serializable>]
type SerializableFSharpClass =
    inherit SerializableBaseClass

    new () as this =
        { inherit SerializableBaseClass() }
        then
        this.InitFields()

    new (other: SerializableFSharpClass) as this =
        { inherit SerializableBaseClass(other) }
        then
        this.InitFields()
        // Copy Properties

    new (info : SerializationInfo, context : StreamingContext) as this
        = { inherit SerializableBaseClass(info, context) } then
        this.InitFields()
        // Deserialize Properties

    // Let binding does not work
    // because of the following error:
    // FS0963: This definition may only be used in a type with a primary constructor.
    //let myFSharpEvent = new Event<EventHandler<EventArgs>,EventArgs>()
    //[<CLIEvent>]
    //member this.FSharpEvent = myFSharpEvent.Publish

    // Event raising does not work with member private
    // because new instances are created with every access to FSharpEvent:
    //member private this.myFSharpEvent = new Event<EventArgs>()
    //[<CLIEvent>]
    //member this.FSharpEvent = this.myFSharpEvent.Publish
    //member this.RaiseFSharpEvent e = this.myFSharpEvent.Trigger e

    // Event raising works with val mutable
    // but requires additional initialization of myFSharpEvent
    [<DefaultValue>]
    val mutable myFSharpEvent : Event<EventHandler<EventArgs>,EventArgs>
    [<CLIEvent>]
    member this.FSharpEvent = this.myFSharpEvent.Publish
    member this.RaiseFSharpEvent e = this.myFSharpEvent.Trigger e

    member private this.InitFields()
        =
        this.myFSharpEvent <- new Event<EventHandler<EventArgs>,EventArgs>()
 

Мне интересно, есть ли более простой способ заставить оба CLIEvent работать с привязкой let и по-прежнему использовать все конструкторы базового класса по мере необходимости, чтобы окончательно избавиться от метода InitFields .

Ответ №1:

Боюсь, здесь вы мало что можете сделать. Проблема в том, что облегченный синтаксис F # для классов с неявным конструктором может использоваться только в том случае, если вы всегда вызываете только один конструктор базового класса. В вашей ситуации это не так, и поэтому вы должны использовать явный синтаксис.

Вы можете избежать DefaultValue атрибута, если инициализируете событие в { .. } части конструктора, где вы можете вызвать конструктор базового класса, а также инициализировать все поля. На самом деле это вам не очень помогает, но это единственная настройка, о которой я могу думать:

 type A = 
  new (n:int) = {}
  new (s:string) = {}

type B =
  inherit A

  new (n:int) = { 
    inherit A(n) 
    myFSharpEvent = new Event<_, _>() }
  new (s:string) = { 
    inherit A(s) 
    myFSharpEvent = new Event<_, _>() }

  val mutable myFSharpEvent : Event<EventHandler<EventArgs>,EventArgs>
  member this.FSharpEvent = this.myFSharpEvent.Publish
  member this.RaiseFSharpEvent e = this.myFSharpEvent.Trigger e
 

Это приводит к некоторому минимальному дублированию кода — я думаю, это нормально, но вы также можете определить вспомогательную функцию (но вне класса, потому что вы не можете использовать let в этом классе).

Итог, вероятно, заключается в том, что я бы убрал всю важную логику из этого класса и рассматривал это только как оболочку, чтобы сделать мой хороший код F # совместимым с любой необходимой вам инфраструктурой .NET.