Replies: 48 comments
-
From @SolalPirelli on February 4, 2015 11:54 You can already assign values to get-only properties from the constructor in C# 6. (source) |
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 12:1 This is not what I am asking. I want this: var student = new Student(new Guid())
{
Name = "John",
Address = GetAddressFromDb(),
Age = minAge + 20
} All properties in the sample above are immutable (readonly). |
Beta Was this translation helpful? Give feedback.
-
From @AlgorithmsAreCool on February 4, 2015 13:6 So you would like to see object initializers gain the ability to write to getter only properties? Like this?
|
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 13:17 Yes, exactly. Otherwise the number of constructor parameters is starting to affect the choice of whether to make something immutable or not. Which is not great at all. Furthermore, it would need to be reflection-friendly to make sure deserialisation (e.g. from JSON) is still possible. |
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 13:22 This is already done for anonymous types (although for a different reason): http://blogs.msdn.com/b/sreekarc/archive/2007/04/03/immutable-the-new-anonymous-type.aspx |
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 13:26 Maybe 'readonly' keyword could be re-used to mark the whole type: //All properties and fields have to be declared as readonly or const and be initialised explicitly.
readonly class Student { ... } This post shares this idea as well: http://madprops.org/blog/immutability-in-csharp-5 I would prefer to see this rather than 'record' which feels kind of alien: name doesn't represent the purpose, syntax is heavily F#-ish etc. |
Beta Was this translation helpful? Give feedback.
-
From @AlgorithmsAreCool on February 4, 2015 13:41 Have you looked at #159 ? It proposes a new immutable type syntax. Your initializer idea might add to that discussion. |
Beta Was this translation helpful? Give feedback.
-
From @SolalPirelli on February 4, 2015 15:53 This can be done with record types (#206) and named arguments. |
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 16:36 Yes, but I don't want a "...type whose semantic meaning is described by the shape of the data..." I just want an immutable class. Record types will be created for a different reason (to facilitate pattern matching). |
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 16:40 Thanks for linking. |
Beta Was this translation helpful? Give feedback.
-
From @agocke on February 4, 2015 19:5
So? If it does what you want, why do you care what it was created for? :) |
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 20:4 When other people will be reading my code I want them to say "OK, so Student is not going to change it's state after instantiation, so I don't have to worry about that, great." and not "OK, so Student isn't representing a domain object but is rather describing a semantic meaning of some data that can be shaped in different ways... I better check what is that data, what are we matching it for, and where is the actual Student domain object, so that I could rename it to something else to avoid confusion with that pattern matching record type...". |
Beta Was this translation helpful? Give feedback.
-
From @agocke on February 4, 2015 20:9 Don't worry -- no one's going to say that. Welcome to the language design. :) We discuss the backing state machines of iterators and the semantic structure of records in pattern matching so someone using the code can just say "Oh, that's an IEnumerable+yield" or "Oh, that's an immutable class I can use pattern matching on." Only the designers think about how the sausage is made. |
Beta Was this translation helpful? Give feedback.
-
From @SolalPirelli on February 4, 2015 20:14 @dsaf: Scala has |
Beta Was this translation helpful? Give feedback.
-
From @ashmind on February 4, 2015 21:46 Idea based on #159 for the type syntax. Let's start with: public immutable class Person {
public Person(string name) {
Name = name;
}
public string Name { get; }
public DateTime Birthday { get; }
...
} We want to do What if public Person(string name) {
Name = name;
} actually generated public Person(string name, [GeneratedForImmutable] Generated_PersonPropertyBag properties) {
Name = name;
// generated
if (properties.NameSet) Name = properties.Name;
if (properties.BirthdaySet) Birthday = properties.Birthday;
} and then var person = new Person("X", new Generated_PersonPropertyBag {
BirthdaySet = true,
Birthday = birthday
}) ? |
Beta Was this translation helpful? Give feedback.
-
@ashmind The proposal for records and pattern matching is #206 Also relevant are 'with' expressions as an extension of that... see dotnet/roslyn#206 (comment) |
Beta Was this translation helpful? Give feedback.
-
From @alrz on October 11, 2015 11:12 This is also useful in attribute usages like public class FooAttribute : Attribute { public string Property { get; } }
[Foo(Property = "value")] Although, it makes more sense to declare this kind of classes in form of a record type, e.g. class Student(Guid guid : Guid)
{
public string Name { get; }
} There is one problem with this, what if you don't want it to be initializable outside of the enclosing type? Maybe |
Beta Was this translation helpful? Give feedback.
-
var person = new Person("X") { Birthday = birthday }. The problem with this notation is that it suggests asymmetry between the properties (where in fact the only difference is that X is passed explicitly and Birthday implicitly). Perhaps consider discouraging this via a code analysis suggestion. Same issue with records type like class Student(Guid guid : Guid)
{
public string Name { get; }
} To keep Reflection clean, consider avoiding generated constructors like public Person(string name, [GeneratedForImmutable] Generated_PersonPropertyBag properties) {
Name = name;
// generated
if (properties.NameSet) Name = properties.Name;
if (properties.BirthdaySet) Birthday = properties.Birthday;
} in favor of the clean, symmetric constructor and perhaps allow public class C
{
public C()
: this(Name: "John", Age: 42) // refer to autogenerated constructor public C(name, age)
{
}
public string Name { get; } // readonly properties the constructor is generated from
public int Age { get; }
}
var c = new C { Name = "Mike", Age = 40 }; // object init syntax
var c1 = new C("Joe", 41); // constructor syntax
var c2 = new C(); |
Beta Was this translation helpful? Give feedback.
-
For the record I don't strongly feel about this anymore. It's not a problem to use constructors and named arguments. Beyond that there is no guarantee that a type is immutable anyway. Besides there would still be a "problem" with immutable collection syntax: http://stackoverflow.com/questions/16882136/can-i-initialize-a-bcl-immutable-collection-using-braces
|
Beta Was this translation helpful? Give feedback.
-
@gafter I hope you used a script to do all that- either way your dedication is admirable! |
Beta Was this translation helpful? Give feedback.
-
I don't think it would be a good idea to combine an actual constructor and initializers. The constructor left the struct in a consistent state and the readonly is a promise you would not change that. So IMHO I think the safe and still helpful solution would be to allow initializers only if there is no constructor in the readonly struct/class definition. public readonly struct Foo {
public readonly int X {get;}
public readonly int Y {get;}
}
...
new Foo{ X = 5, Y = 15}, new Foo{ X = 99} would thus become public reaonly struct Foo {
public Foo(int x = default(int), int y = default(int)) { this.X = x; this.Y = y; }
public readonly int X {get;}
public readonly int Y {get;}
}
...
new Foo(x: 5, y: 15), new Foo(x: 99) |
Beta Was this translation helpful? Give feedback.
-
Well, I guess it sucks to be you, because that scenario has always been legal. |
Beta Was this translation helpful? Give feedback.
-
@Joe4evr In case of readonly structs? I don't think so. |
Beta Was this translation helpful? Give feedback.
-
That syntax is legal anywhere else initializers are permitted, though. It would be inconsistent to not allow that here. Either way, initialization via generated constructors is inherently fragile. Something which should be innocuous like reordering the members will result in breaking binary changes due to the regenerated constructor. Last I recall the team was looking at instead generating a mutable struct builder and supporting that for readonly initialization. |
Beta Was this translation helpful? Give feedback.
-
@JendaPerl Technically, yes. |
Beta Was this translation helpful? Give feedback.
-
It's not allowed now for readonly structs and IMHO it should not be. Either constructor or initializers, not both. If I need to set things up so that the whole structure is in a consistent state, I use a constructor and as I set the structure to readonly I do not expect the state to change. If the structure is just a dumb data store, then having to write a constructor is tedious and error prone.
As the constructor would be called "behind the scenes" and using named parameters, there is no reason the parameters could not be sorted alphabetically instead of being left in the "order of appearance". Then you'd only have a breaking binary change if you added or removed properties or changed their types.
Whatever works :-) |
Beta Was this translation helpful? Give feedback.
-
It has always been allowed for readonly structs. Did you see the sharplab example I linked? This was legal code the day the static class C
{
static void M()
{
var foo = new ReadOnlyStruct(42)
{
BarName = "From initializer"
};
}
}
public readonly struct ReadOnlyStruct
{
private readonly Bar bar;
public string BarName { get => bar.Name; set => bar.Name = value; }
public ReadOnlyStruct(int x)
{
bar = new Bar();
}
}
public class Bar
{
public string Name { get; set; }
} The reason that this is consistent is that |
Beta Was this translation helpful? Give feedback.
-
And this is useful how?!? You bend backwards to add a non-readonly property to a struct marked readonly and then and only then you are able to mention it in an initializer? COOL! The point is to be able to INITIALIZE the readonly properties as I instantiate the struct, not whether you can circumvent the readonly mark on a struct to add something that's not readonly. |
Beta Was this translation helpful? Give feedback.
-
As much as the proposed feature would be handy, I think that being explicit via constructors is more helpful. It seems to help with design of the class in question: often the initializer syntax is an "organic growth" on top of well thought-out constructors, and indicates a need to refactor things. For classes that have a clear and sensible™ need for combined constructor + initializer syntax, the property bag / builder type pattern should work well. Those should be few and far between. So, while I initially thought that this was a very good idea, I don't think so anymore. |
Beta Was this translation helpful? Give feedback.
-
Being explicit is insanely chatty in case of the majority of classes whose sole purpose of existence is to hold some data. There's nothing to design there, I just want to pass around the data I just read from somewhere. |
Beta Was this translation helpful? Give feedback.
-
From @dsaf on February 4, 2015 9:34
Immutable types are a best practice nowadays. However, when a C# class has a lot of properties it means ctor will have lots of parameters. Property initializers are easier to read and understand as well.
Can we have a way of initializing properties on immutable types? I have read about C# 6 auto-property initializers and unless I am mistaken they only help with compile-time constants.
PS: please don't do this by introducing new types (e.g. why have some sort of 'record' when we already have classes and structs?).
Copied from original issue: dotnet/roslyn#229
Beta Was this translation helpful? Give feedback.
All reactions