Note: the proposed API was modified significantly during API design review.
See notes below: https://github.com/dotnet/runtime/issues/34978#issuecomment-614845405
The type will be used as a modreq on the return type of the set accessor:
public modreq(typeof(IsExternalInit) void set_InitOnlyProperty(string value) { ... }
Out-of-date:
As part of the C# 9 records feature, we want to allow some properties to be settable during "initialization". Initialization means construction and also object creation. So we would allow new C() { InitOnlyProperty = "hello" }
.
To declare such a property, we'll allow the following syntax:
public string InitOnlyProperty
{
get { ... }
init { ... } // this is an init-only setter
}
We will emit this property as:
// pseudo code
private readonly string backingField;
[InitOnly]
public void InitOnlyProperty_setAccessor(modreq(typeof(InitOnlyAttribute)) string value) { ... }
The attribute is used on the set accessor and in a modreq.
There are two potential expansion of the feature: init-only fields and init-only methods. We can discuss whether it is better to include those now, or add later.
So we are proposing to add the following API:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class InitOnlyAttribute : Attribute
{
}
}
Tagging @jaredpar @agocke FYI
Updates:
I like this idea. I've got a situation where I'm dependency injecting a buffer into a specialized stream wrapper, which uses inversion of control. Without init-only, the interfaces have to keep a public getter/setter for the underlying stream, which isn't a huge issue but certainly causes a leaky abstraction that this would resolve.
The attribute is used by the compiler for two purposes:
- on the backing field
The proposed API you got allows it on AttributeTargets.Method
only. Which one is correct?
Note that fields have FieldAttributes.InitOnly
flag already. Having this attribute on fields is going to be confusing.
Thanks. I fixed the proposed API to have AttributeTargets.Field
.
Whether we'll want to introduce the notion of init
methods (init void Initialize() { ... }
) is an open question for LDM.
Yes, the name is unfortunate, as IL already has a flag called initonly
used for emitting C#'s readonly
.
Why do you need the attribute for fields? Why is not the existing FieldAttributes.InitOnly
sufficient for fields?
Is the backing field really supposed to be readonly
?
ECMA-335 doesn't seem to allow this (emphasis mine):
initonly
marks fields which are constant after they are initialized. These fields shall only be mutated inside a constructor. If the field is a static field, then it shall be mutated only inside the type initializer of the type in which it was declared. If it is an instance field, then it shall be mutated only in one of the instance constructors of the type in which it was defined. It shall not be mutated in any other method or in any other constructor, including constructors of derived classes.
ECMA-335 doesn't seem to allow this
You're not wrong, but the runtime doesn't fully enforce that restriction (use of the word "shall" means it's looser than "must"). In the recent .NET Language Standup it's said that they intend to exploit this fact that the runtime allows changing readonly instance fields without a problem in order to implement this feature.
Right. Also, the ECMA-335 spec is not set in stone. It has been relaxed and augmented for number of other new runtime features. (We do not have a way to publish these edits today - but I hope that is going to change.)
I'd be very nervous with any feature taking a dependency on initonly instance fields being mutable. That might tie our hands at making future optimizations. Recall that the core runtime forbids setting initonly static fields outside the cctor, even though the ECMA spec uses "shall" instead of "must".
I'd be very nervous with any feature taking a dependency on initonly instance fields being mutable.
This makes the initonly instance fields mutable in a specific context only (only in methods tagged this attribute). I do not see why it would tie our hands at making future optimizations.
Was the plan not also to allow this syntax? Did I misunderstand?
class MyClass
{
public init readonly string PublicField;
// property below has its own compiler generated backing field
public string PublicProperty { get; init; }
}
var obj = new MyClass
{
PublicField = "hi!",
PublicProperty = "hello!"
};
Why do you need the attribute for fields? Why is not the existing FieldAttributes.InitOnly sufficient for fields?
I think we could omit [InitOnly] attribute from backing fields on auto-properties. But we would need it if bring back init-only fields (was in initial design, but is now out-of-scope).
Jared agreed with that. I'll update the proposal.
@GrabYourPitchforks Init-only fields were pushed out of scope for C# 9 in Monday's LDM discussion. Notes should be available shortly. We could add this back (in C# 9 or later) based on scenarios and feedback.
Thanks all for the context - much appreciated! :)
Init-only fields were pushed out of scope for C# 9 in Monday's LDM discussion. Notes should be available shortly.
I very much prefer this approach first over depending on the aforementioned runtime quirk. 馃憤
modreq
s.PropertyInfo.SetValue
could block setting init only.init
setters (as in, they shoud be able to call them)Is
to be consistent with the other modifier typesIsInitOnly
would be unfortuantel becaue initonly
already has meaning in IL/the runtime. We settled on IsExternalInit
.C#
namespace System.Runtime.CompilerServices
{
public sealed class IsExternalInit
{
}
}
I share the concern that initonly
means something very different at the runtime level. I think that initonly
is a good name for the C# language and a bad name for the runtime layer.
I think a better name would be IsExternalInit
. This is much closer to how the actual feature looks at the runtime level.
Oh, apparently we missed that concern. Yeah, since initonly
is an IL keyword, we should probably not use that. I like IsExternalInit
.
@dotnet/fxdc, anybody else?
IsExternalInit
isn't a great name, but if it's only used by modreq then it's not really in anyone's face, and it seems good enough.
Not sure if it will be a modreq or an actual attribute, but either way it will be turned into syntax in C#, so it will not be commonly visible.
Did you have a better suggestion? I was just proposing staying away from initonly
Nah, @bartonjs is just disappointed that the name doesn't include ALL_CAPS or a crypto algorithm ;-)
We discussed the unfortunate naming in relation to initonly
flag in the thread above.
Personally, I think IsInitOnly
is fine because it is only used by the compiler (does not appear in user code anyways).
Personally I think that's the exact reason why it's a bad name. If it's mostly metadata-level visible (the only way you'll see it is if you're looking directly at metadata), it should use metadata-relevant names. Remember that we'll be encoding this into IL-verification, so this should become a part of a new metadata specification in the future.
Is InitOnlyProperty_setAccessor going to be declared an accessor of InitOnlyProperty and marked special named?
Regarding Validators and reflection: I presume that the implementation of validators will be something that's easily identifiable from metadata. Because of that, I think it'd make sense to at least allow reflection scenarios to explicitly call a validator, even if normal C# can't. A strawman of that may look something like this:
public abstract partial class Type
{
public virtual void InvokeValidatorIfExists(object obj); //no-op if the type has no validator
}
This can go out into a separate discussion if it comes to that, but since it briefly came up in the design meeting, I figured I'd put my 2 cents here first.
Is InitOnlyProperty_setAccessor going to be declared an accessor of InitOnlyProperty and marked special named?
@jcouv should answer this, but based on how I understand the compiler feature I'd expect that set_InitOnlyProperty()
will be marked just like any other set accessor with the only difference that the return type is modreq'ed with IsExternalInit
.
Metadata for the init accessor is being discussed in https://github.com/dotnet/csharplang/issues/3376.
@jcouv Is the IL called out above out of sync with the current design?
Above states:
public void InitOnlyProperty_setAccessor(modreq(typeof(InitOnlyAttribute)) string value) { ... }
But on other threads, it appears to be:
public modreq(typeof(InitOnlyAttribute)void set_InitOnlyProperty(string value) { ... }
Yes, the modreq was moved to the return type following API design review.
The type also is no longer InitOnlyAttribute
, but IsExternalInit
(which isn't an attribute).
public modreq(typeof(IsExternalInit) void set_InitOnlyProperty(string value) { ... }
(Updated OP to reflect)
Most helpful comment
modreq
s.PropertyInfo.SetValue
could block setting init only.init
setters (as in, they shoud be able to call them)Is
to be consistent with the other modifier typesIsInitOnly
would be unfortuantel becaueinitonly
already has meaning in IL/the runtime. We settled onIsExternalInit
.C# namespace System.Runtime.CompilerServices { public sealed class IsExternalInit { } }