Inline C# Object Creation in F#

Viewed 137

I'm trying to interop with a C# library in some F# code. Consider the following C# as though it were the library I'm working with (or skip below to see the actual library I'm working with first):

public class Options
{
    public Options(string name)
    {
        Name = name;
    }

    public string Name { get; }
    public string SomeProperty { get; set; }
}

public class ServiceBuilder
{
    public ServiceBuilder ApplyOptions(Options options)
    {
        //Apply Options in some way
        return this;
    }

    public TheService Build()
    {
        return new TheService();
    }
}

public class TheService
{
}

I'm then trying to create the service but keeping it fluent I have the following F# code:

//Valid Approach but not inlined :(
let options = Options("Test")
options.SomeProperty <- "SomeValue"

let theService = 
    ServiceBuilder()
        .ApplyOptions(options)
        .Build();
//Invalid Approach because SomeProperty is not virtual
let theService2 =
    ServiceBuilder()
        .ApplyOptions({
            new Options("Test2") with
                member _.SomeProperty = "SomeValue2"
        })
        .Build()

Is there some way for me to initialize the way I want to inline in F# where I try to create "theService2"? In C# I'd just use Object Intializers. F# Object Expressions are out because I don't have control of the class to make the property virtual.

For additional context in what my C# above is mocking, I'm specifically trying to create a Serilog Logger using the Serilog.Sinks.ElasticSearch nuget package and do roughly the code below in F# (again, inlined if possible):

var loggerConfig = new LoggerConfiguration()
    .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200") ){
         AutoRegisterTemplate = true,
         AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6
     });
2 Answers

For direct translation from C# - property initializers are the way to go, as suggested in @rob.earwaker's answer.

However, note also that in F# everything is an expression. There are no "statements" like in C#, every piece of code has a result of some kind. And this also goes for "composite", so to say, pieces of code, such as let blocks. This means, even if you don't feel like using property initializers, you can still do the initialization inline:

let service =
  ServiceBuilder()
    .ApplyOptions(
      let o = Options("Test")
      o.SomeProperty <- "SomeValue"
      o
    )
    .Build()

Or using let .. in and a semicolon to put everything on the same line:

let service =
  ServiceBuilder()
    .WithOptions(let o = Options("Test") in o.SomeProperty <- "SomeValue"; o)
    .Build()

Unlike C#, this approach also works for factoring out initializations into reusable pieces:

let service =
  ServiceBuilder()
    .WithOptions(let o = Options("bar") in mutateSomeOptions(o); mutateOtherOptions(o); o)
    .Build()
Related