Replies: 26 comments
-
I can confirm that's what I do. This is also why records (or readonly fields/props in general) should support object initializers (nominal construction in the notes). |
Beta Was this translation helpful? Give feedback.
-
As long as the goal is to make C# more friendly to immutable, it will not be a single gap to fill. I think it is inevitable to have several new features considered together. |
Beta Was this translation helpful? Give feedback.
-
I think an important step in finding a solution is to look at the three main syntaxes that people use for construction, their popularity, and the benefits of each. My thoughts are below:
I use this syntax often but I dislike it because it makes it difficult to understand the use of each parameter. If I want to know what a parameter is being used for, I have to select that method and look at the tooltip that appears on the parameters.
I actually like the clarity of this form but I never use it in my code. I think that the C# guidelines around parameter naming and member naming tend to discourage this and the lack of comfortable editor support discourage this as well. I feel like the casing difference on the parameter names almost makes it feel like I'm working with a totally different object. I also dislike that it requires me to write a meaningless constructor to initialize my object with the values of the parameters (1 has this problem too(. ie:
I really like this syntax and use it often. It has great editor support, however, I dislike the fact that it requires me to make all my members mutable. My preference would be to see a syntax that extends construction syntax to provide the clarity and ease of 3 while making it easy to add/remove member. I propose syntax similar to the following.
Here is an example:
Any properties specified as auto are essentially named parameter for the constructor that can be passed by name only (the values can not be passed by position). It is an error for any auto constructor to have a parameter whose name matches an auto property. When an auto constructor is called, the classes auto properties should already be initialized with their provided values. Creating a class with auto properties would use this familiar syntax:
It would be an error to try to call the constructor as follows:
The above would result in an overload resolution error. |
Beta Was this translation helpful? Give feedback.
-
Even if the syntax appears to be nominal instead on positional to be encoded into a constructor parameter forces the call to be positional in IL. That makes construction brittle to code changes that would otherwise not break contracts, such as reordering the declaration order of parameters. And that doesn't even take into consideration how ordering would work across a partial class definition. I'm still firmly in the camp of having nominal data types emit a builder pattern which the compiler would support through object initializer syntax. Every other option I've seen is either brittle for similar reasons I've mentioned above or relies on bizarre metadata trickery which will not work well, if at all, with downlevel compilers or across the .NET ecosystem. Nothing as "simple" or prevalent as a POCO should require anything new or exotic. |
Beta Was this translation helpful? Give feedback.
-
Please consider generating |
Beta Was this translation helpful? Give feedback.
-
I prefer property to have 3 accessors: public string Name { get; init; private set; } // initialization is mandatory
public string Name { get; init?; private set; } // initialization is optional
public string Name { get; init?; set; } // warning: optional initializer is redundant
public string Name { get; init?; } // immutable property
private string _name;
public string Name {
get => _name;
private set => _name = value;
init; // auto implemented init defaults to invoking setter
}
private string _name;
public string Name {
get => _name;
init => _name = value; // explicitly implemented initializer
set => SetProperty(ref _name, value); // setter with INotifyPropertyChanged event invoker
}
class C {
public string Name { get; init; }
public C(string name) {
Name = name;
}
}
C obj = new C("name"); // name already initialized in constructor. no error.
class C {
private string _name;
public string Name {
get => _name;
init => _name = value;
}
public C(string name) {
_name = name; // initalize backing field instead of the property
}
}
C obj = new C("name"); // error: Name was not initialized. |
Beta Was this translation helpful? Give feedback.
-
I suggest this solution: Step 2: var x = new Person(){
init ID = 1,
Name = "John Smith",
}; Step 3: var x = new Person(id : 1); // call the key initializer constructor
// There maybe other keys but they are optional and set to their defaults.
x.Name = "John Smith"; // set other public properties normally The intillisense will make things easier if it shows a list of all keys after typing the init keyword, and appends the : after the selected name. Note that we have another way to write the initialization code: var x = new Person(1){
Name = "John Smith",
}; Just call the Key constructor and initialize public properties. In this case no lowering is needed. Note 2:
but it maybe confusing! |
Beta Was this translation helpful? Give feedback.
-
That would mean that: public class Person {
public key int ID;
public key string Name;
} and public class Person {
public key string Name;
public key int ID;
} would produce two incompatible constructors. What seems like an innocuous refactor could completely break consuming code, or worse, lead to property values being swapped unexpectedly at runtime. It also wouldn't at all work with |
Beta Was this translation helpful? Give feedback.
-
Frankly, I am not aware of all these details.
Make all the changes to the language to allow this. |
Beta Was this translation helpful? Give feedback.
-
We can call the key: a write-once property! So, no need to the readonly in the definition! The key is a write once property! |
Beta Was this translation helpful? Give feedback.
-
Note that C# can change the value of a readonly property by reflection. So, it is possible and all we need is to make Roslyn accept the syntax in the intiallizer, then C# uses Reflection. I think. |
Beta Was this translation helpful? Give feedback.
-
Reflection is inherently brittle and very slow. It would make the consuming application take a hard dependency on what happens to be the name of the backing field. Changing that name, which also should be an innocuous refactoring, will now break consuming code. If the property is writable at all it is always writable. There's really nothing that the C# language can do to enforce that a property is only written once. Downlevel compilers and other languages can simply ignore that magic metadata and call the setter whenever they please. |
Beta Was this translation helpful? Give feedback.
-
This is not true. You can change a readonly property in the constructor. All we need is to extend this behavior to include the initializer block! I suggested to make this possible only for keys to make a more sense for this special case. |
Beta Was this translation helpful? Give feedback.
-
Constructor code is encapsulated entirely within the declaring class, where it is perfectly legal to assign an
Initializer blocks don't exist in the runtime. The compiler converts them into calls to the writable properties on that type. var person = new Person() {
Name = "Somebody"
};
// is the same as
var person = new Person();
person.Name = "Somebody"; And if a writable property exists on the type it can be invoked from anywhere in the code any number of times. There's nothing that the C# compiler can do to prevent that. Even if the compiler emitted some magic metadata onto the writable property that supporting compilers would recognize and enforce that wouldn't stop older versions of C# or any other language in the ecosystem from ignoring it and just overwriting that property. So the feature request here is to extend initializer syntax in some manner so that it supports write-once initialization. The obvious place to do that is the constructor, but you can't simply convert write-once properties as constructor parameters without causing the brittleness problems around parameter ordering that I mentioned above. The strategy I've proposed would be to follow the builder pattern, in that the compiler emits a mutable nested "builder" type where you can initialize the values and then that initialized builder is passed to the constructor to actually initialize the type: var person = new Person {
ID = 123,
Name = "Somebody";
};
// turns into:
var $builder = new Person.Builder();
builder.ID = 123;
builder.Name = "Somebody";
var person = new Person(builder); It's simple, clean and already exists as a common pattern for constructing immutable types across innumerable languages and ecosystems. Language support for builder patterns in general could be an orthogonal feature on which nominal records could be built, and all existing compilers and languages in the ecosystem would automatically understand how to interop with them. However, members of the language team think that this approach is brittle in other ways, particularly around inheritance. IMO, it's worth working through those issues vs. trying to surface pseudo-write-once members or public properties hidden behind magic metadata and a wink and nudge. |
Beta Was this translation helpful? Give feedback.
-
I see no harm in ignoring older versions or other languages. Every library author documents the necessary roles of using his library, so he can warn against changing these keys. |
Beta Was this translation helpful? Give feedback.
-
that's not really a viable strategy. people can and do use older versions of the language/compiler for years after new releases come up. |
Beta Was this translation helpful? Give feedback.
-
I think initializing properties in constructor and specifying default value can also be considered as "write" class A {
public string S { get; }
public long L { get; } = 100; // 'write' once
public A(string s) {
S = s; // 'write' once
}
} Aren't these writes what we are trying to improve? |
Beta Was this translation helpful? Give feedback.
-
There is an easy solution for this. No need to modify the constructor params. C# can directly inject the key initialization statment at the end of the public constructor, or just move them to a private parameterless constuctor that is called from all available constructors. Any old versions will not be compatible with the new Record and key syntax, so make sure it can't reference these assemblies. I think this is a fair trade and any one who want background compatibility has to give up recors and write more lines of code. |
Beta Was this translation helpful? Give feedback.
-
As i mentioned: people can and do use older versions of the language/compiler for years after new releases come up. Breaking the ecosystem so that the entirety of the existing userbase cannot reference assemblies created in C# 9 would be bad. |
Beta Was this translation helpful? Give feedback.
-
I am not talking about all C# 9 assemblies, but just those that use records and keys in the suggested way. This is a fair exception, and as I said, anyone wants to maintain backward combinability can simply give up records / keys in such an assembly. Everyone still gets what he wants and we will be all happy. |
Beta Was this translation helpful? Give feedback.
-
Subjectively maybe, not objectively. |
Beta Was this translation helpful? Give feedback.
-
Do you mean OOP-ively? |
Beta Was this translation helpful? Give feedback.
-
I would like to create features that can be used without preventing people from targeting the vast userbase out there not on the latest version. I'm not getting what I want here :-) |
Beta Was this translation helpful? Give feedback.
-
This route leads to different teams/projects using incompatible subsets of the language. Just look at the pain endured by the Python ecosystem over the last decade to get an idea why that’s a situation we all should want C# to carefully avoid. |
Beta Was this translation helpful? Give feedback.
-
@theunrepentantgeek hasn't the Microsoft c# management group (not sure which actual group of people makes these decisions) just done that by deciding that framework won't implement standard 2.1?, while c#8 needs it ( Core 3.0 / standard 2.1) ? |
Beta Was this translation helpful? Give feedback.
-
I don't think so @AartBluestoke - they're only superficially similar. The C# 8 feature Default Interface methods (DIM) requires runtime support to work and the decision was made that the .NET CLR won't be modified to add that support. The dividing line is between those using C# 8 on .NET Core (the latest version) and those using C# 7.2 on .NET Framework (effectively legacy versions). The suggestion above was to create a new C# feature that would divide the ecosystem of those using C# vNext on .NET vNext - while both groups would be using the latest versions of the both the compiler and the run-time, they wouldn't necessarily be able to share code. Put another way, the decisions around DIM partition users of .NET into "those able to move forward to the latest version" and "those unable to move forward". This division always exists when a new version (of almost any language or library) is released, and the C# LDM goes to great lengths to ensure that almost everyone is able to move forward easily. Plus, over time, a large proportion of those in camp "unable" find ways forward. Creating a division between different groups of developers who have already moved forward to the latest version seems to me to be unnecessary and counterproductive. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-12-11.md
Agenda:
Feedback from design review
Beta Was this translation helpful? Give feedback.
All reactions