Proposal: Final Initializers #6591
Replies: 8 comments 17 replies
-
If this can ensure that instances of a type are always in a valid state, it would be a great addition for record types, which currently allow bypassing of constructor validation checks through the use of the |
Beta Was this translation helpful? Give feedback.
-
Great! A few questions:
|
Beta Was this translation helpful? Give feedback.
-
I really like that idea. |
Beta Was this translation helpful? Give feedback.
-
I suggested it in another thread and I'm posting it here. Maybe it can be easier/more straightforward to use IAsyncLifetime as a first citizen interface like IEnumerable. |
Beta Was this translation helpful? Give feedback.
-
For my understanding the |
Beta Was this translation helpful? Give feedback.
-
Is there any movement on this proposal? I really like required properties, but I'd also like to have an ability to validate them without explicitly writing setters for each required property and also to compute other non-settable properties using values from the required ones. |
Beta Was this translation helpful? Give feedback.
-
We (Metalama / PostSharp) support this proposal. Use casesFinal initializers are an important feature in implementing the following patterns:
AnalogiesThis feature shares similarities with the It would be useful for the final initializer to be exposed as an interface method so that deserializers could use this method instead of a custom one. An public enum InitializationReason
{
// Explicit use of the constructor with or without field/property initialization clauses.
// This value is typically used by the compiler.
Construction,
// The object was deserialized. Typically used by the deserialization library.
// Typically a post-deserialization callback should be allowed to have access to `init`-only fields
Deserialization,
// The object was cloned. (To be considered)
Clone
} DifficultiesA difficulty in classes that are part of class libraries is that they may be used by a compiler or library that does not understand the new specification. However, for some use cases, it is crucial that the constructor knows that the final initializer will be invoked. For instance, when implementing change tracking, I might want to enable change tracking immediately before the constructor exits, instead of in the final initializer, if I know the final initializer won't be called. A possible solution is to add another element to the specification: an optional constructor parameter of type
If a compiler or a library implements the specification, it must support both elements:
Final initializers are very useful in implementing several patterns, and we're looking forward to this feature. |
Beta Was this translation helpful? Give feedback.
-
I really want to voice support an justification for this proposal. Having properties that we can't validate or post-process as a collection, not just indivdually, seems to go against encapsulation principles. I don't agree that fixing that represents a complication to the language. Maybe init properties should have come before the constructor, but they didn't, and that's where we are. An example in real code:This is a real piece of draft code I wrote as a workaround: public class Isotope {
//init parameters
public required string IsoName { get; init; }
public required string Element { get; init; }
//init parameters with dependencies
public required int N { get; init { field = value; UpdateDerived(); } }
public required int Z { get; init { field = value; UpdateDerived(); } }
public double HalfLifeSeconds { get; init { field = value; UpdateDerived(); } } = 0;
//derived properties,
public int A { get; private set; }
public double TauSeconds { get; private set; }
public double LambdaSeconds { get; private set; } // Decay constant (λ) in 1/s
//Update Derived Properties
private void UpdateDerived() {
A = Z + N;
TauSeconds = HalfLifeSeconds / Math.Log(2);
LambdaSeconds = Math.Log(2) / HalfLifeSeconds;
}
...
} Clearly this is verbose, I am forced to call code at least twice that only needs to run once, and those private set specifiers are not ideal. They don't promise immutibility. But obviously this would both look better and function better: public class Isotope {
//init parameters
public required string IsoName { get; init; }
public required string Element { get; init; }
//init parameters with dependencies
public required int N { get; init;}
public required int Z { get; init;}
public double HalfLifeSeconds { get; init;} = 0;
//derived properties,
public int A { get; }
public double TauSeconds { get; }
public double LambdaSeconds { get; } // Decay constant (λ) in 1/s
//Update Derived Properties
init() {
A = Z + N;
TauSeconds = HalfLifeSeconds / Math.Log(2);
LambdaSeconds = Math.Log(2) / HalfLifeSeconds;
}
} Much cleaner, much less duplication, no more private set. This particular example could be handed with expression-bodied members, but using public double TauSeconds => HalfLifeSeconds/ Mat.Log(2); Adds a computation on every get, and I will likely be using this in an fast loop. Feasibility and implementationThis meeting note Indicated that it's very complicated. And it may be. I'm no compiler programmer. But I notice that aside from maybe fixing the private set problem, I could in principle do it right now using a pre-processor or manually:
Maybe there are better or more desirable implementations, but it can't be too evil or full of gotchas because this way already seems pretty good, and is already supported, useable mechanics now. We just don't have syntax enforcement for 2 and 3 in particular, which are important but are just enforcement, not new mechanics. Basically a linter could do that. Existing AlternativesIn this case, I could just use traditional ctor parameters and readonly ({get;}) properties and probably should. But if you start with init parameters then you're stuck if you need to add on derived values later. Also init paramers seem to me to have advantages for setting default values with less verbosity, maybe especially with inheritance. At present though, init properties seem like a feature that isn't quite there, maybe should even be avoided. (Edited for code highlighting) |
Beta Was this translation helpful? Give feedback.
-
Let's talk about final initializers.
The Final Initializers proposal
Consider a constructor that expects a value in a range, such as 1-12 for months. Currently, if you switch to initializers you cannot do this validation. Also, multiple constructors must explicitly do this validation, or call constructors that do.
The proposed final initializer would run after all construction steps and be a logical home for this type of validation.
Beta Was this translation helpful? Give feedback.
All reactions