C# object initialization syntax in F#
Asked Answered
U

3

15

Please note: this question is not the same as this question.

I recently came across some C# syntax I hadn't previously encountered:

Is there any way to do this in F#?

class Two
{
    public string Test { get; set; }
}

class One
{
    public One()
    {
        TwoProperty = new Two();
    }
    public Two TwoProperty { get; private set; }
}

var test = new One(){ TwoProperty = { Test = "Test String" }};

(note the initialization of TwoProperty in the initializer when the setter is private - it is setting a property on the object stored in TwoProperty, but NOT storing a new instance of Two in the property)

Edit: I recently came across some C# code in a constructor in monotouch like this:

nameLabel = new UILabel {
    TextColor = UIColor.White,
    Layer = {
        ShadowRadius = 3,
        ShadowColor = UIColor.Black.CGColor,
        ShadowOffset = new System.Drawing.SizeF(0,1f),
        ShadowOpacity = .5f
    }
};

The best F# translation I could come up with was something like this:

let nameLabel = new UILabel ( TextColor = UIColor.White )
do let layer = nameLabel.Layer
   layer.ShadowRadius <- 3.0f
   layer.ShadowColor <- UIColor.Black.CGColor
   layer.ShadowOffset <- new System.Drawing.SizeF(0.0f,1.0f)
   layer.ShadowOpacity <- 0.5f  

This isn't terrible, but it does have more noise with the repeated layer reference plus its more imperative and less declarative.

Urdar answered 5/5, 2014 at 2:10 Comment(4)
Show us what you've tried so far. What did you find difficult?Noctambulism
Is this a documented feature of C#? Allowing a private setter to be called in this context seems like a bug.Masurium
@Masurium It isn't calling a private setter. It's setting properties on the object contained in the setter. See the F# translation for clarify on what the C# is doing.Urdar
I see. Sorry, I looked over it too quickly. I don't think this is possible in F#.Masurium
S
4

Would it make sense to encapsulate the construction into a separate initializer function?

let layerInit layer radius color offset opacity =
    do
       layer.ShadowRadius <- radius
       layer.ShadowColor <- color
       layer.ShadowOffset <- offset
       layer.ShadowOpacity <- opacity
    layer // I do this in case you want to use this fluently or pass in new Layer()

Then use that in your code:

let nameLabel = new UILabel ( TextColor = UIColor.White )
layerInit nameLabel.Layer 3.0f UIColor.Black.CGColor new System.Drawing.SizeF(0.0f,1.0f) 0.5f |> ignore
Scrapple answered 5/5, 2014 at 15:25 Comment(1)
I like this idea. I think if I were going to do a lot of development with this framework, I would probably create a helper function with all available properties as optional parameters so that the parameters would be named.Urdar
M
6

I don't think F# allows setting nested properties during initialization. A workaround, assuming you're the author of the API, is to pass the entire object to the constructor. This eliminates the need for getter and setter with different accessibilities and makes for much cleaner F# code overall.

type Two() =
    member val Test = "" with get, set

type One(twoProperty) =
    member val TwoProperty = twoProperty

let test = One(Two(Test="foo"))

As you mentioned in your comment, you could create a helper function accepting various properties as optional parameters. A type extension would work well for this:

type UILayer with
    member this.Configure(?shadowRadius, ?shadowColor, ?shadowOffset, ?shadowOpacity) = 
        this.ShadowRadius <- defaultArg shadowRadius this.ShadowRadius
        this.ShadowColor <- defaultArg shadowColor this.ShadowColor
        this.ShadowOffset <- defaultArg shadowOffset this.ShadowOffset
        this.ShadowOpacity <- defaultArg shadowOpacity this.ShadowOpacity

let nameLabel = UILabel(TextColor=UIColor.White)
nameLabel.Layer.Configure(
    shadowRadius=3.0f, 
    shadowColor=UIColor.Black.CGColor, 
    shadowOffset=SizeF(0.0f, 1.0f), 
    shadowOpacity=0.5f)
Masurium answered 5/5, 2014 at 14:58 Comment(1)
Sadly the class I'm constructing is a UILabel which is a Cocoa Touch framework class.Urdar
S
4

Would it make sense to encapsulate the construction into a separate initializer function?

let layerInit layer radius color offset opacity =
    do
       layer.ShadowRadius <- radius
       layer.ShadowColor <- color
       layer.ShadowOffset <- offset
       layer.ShadowOpacity <- opacity
    layer // I do this in case you want to use this fluently or pass in new Layer()

Then use that in your code:

let nameLabel = new UILabel ( TextColor = UIColor.White )
layerInit nameLabel.Layer 3.0f UIColor.Black.CGColor new System.Drawing.SizeF(0.0f,1.0f) 0.5f |> ignore
Scrapple answered 5/5, 2014 at 15:25 Comment(1)
I like this idea. I think if I were going to do a lot of development with this framework, I would probably create a helper function with all available properties as optional parameters so that the parameters would be named.Urdar
U
1

F# 4.0 came out with a feature which might be useful to encapsulate @plinth's answer. It allows the creation of extension properties, which can be initialized in the constructor.

Urdar answered 18/5, 2015 at 0:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.