Init-only setters, introduced in C# 9, allow for a concise alternative to named optional constructor parameters. Let's illustrate and then discuss the pros and cons.
As you can see in the example below, the amount of repetition involved in getting a private readonly field initialized with an optional constructor parameter (that's a mouthful, but it's a common thing to do) has traditionally been insane. I have to write the name of each field 4 times and specify their type twice to get a readonly optional field.
class C1
{
readonly Action onOpen;
readonly Action onClose;
readonly Action onBegin;
public C1(
Action onOpen = null,
Action onClose = null,
Action onBegin = null)
{
this.onOpen = onOpen;
this.onClose = onClose;
this.onBegin = onBegin;
}
// ...
}
// usage:
var c1 = new C1(
onClose: () => Console.WriteLine("OnClose called")
);
As it turns out, properties marked as { private get; init; }
can fill the same role with no code repetition, cutting down clutter by two thirds!
class C2
{
public Action OnOpen { private get; init; }
public Action OnClose { private get; init; }
public Action OnBegin { private get; init; }
// ...
}
// usage:
var c2 = new C2()
{
OnClose = () => Console.WriteLine("OnClose called")
};
As the getter is private, the property can only be accessed in the object initializer, making it equivalent, for our purposes, to a named optional constructor parameter.
Since this class has no required constructor parameters, it can do away with a constructor entirely. This pattern could even work with required parameters in the future, if required init-only properties make it into a future C# version, C# 10 or C# 11 perhaps? The syntax would hypothetically look like this:
class C2
{
public required Action OnOpen { private get; init; }
public required Action OnClose { private get; init; }
public Action OnBegin { private get; init; }
// ...
}
// usage:
var c2 = new C2()
{
OnClose = () => Console.WriteLine("OnClose called"),
// compilation error: OnOpen must be provided
};
4 or 5 keywords per property is a bit intense to be sure, but I'd take it over the sheer massive boilerplate it replaces.
I'm not sure at this point what the drawbacks would be. A couple I can see so far:
- The property appears in code completion since it's public. It won't be possible to use it outside the object initializer, but this might be a bit confusing. This is more of a glitch of code completion than anything else.
// code completion will suggest OnClose at this point
Console.WriteLine(c2.
// but it's inaccessible, so you just get a compiler error
Console.WriteLine(c2.OnClose)
// code completion could be smarter, but that's what we have today
- It creates a slight discoverability issue for the consumer, as all constructor parameters appear in code completion, but init-only properties only appear when opening an object initializer, which a user might just forget to do, or wonder how the object is to be used.
var c1 = new C1( // see list of constructor arguments appear
var c2 = new C2( // code completion shows nothing at this point
var c2 = new C2() { // only now you get your property names
Both of these issues mainly stem from unfamiliarity with the pattern, so I don't think they're necessarily roadblocks.
What do you all think?
Top comments (0)