Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Required Properties #3630

Open
Tracked by #829
333fred opened this issue Jun 30, 2020 · 497 comments
Open
Tracked by #829

[Proposal] Required Properties #3630

333fred opened this issue Jun 30, 2020 · 497 comments
Assignees
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion Proposal
Milestone

Comments

@333fred
Copy link
Member

333fred commented Jun 30, 2020

Required Properties

Specification: https://github.com/dotnet/csharplang/blob/master/proposals/csharp-11.0/required-members.md

Design meetings

https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-16.md#required-properties
https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-07.md
https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-01-11.md
https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-03-03.md#required-members
https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-25.md#required-members
https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-12-15.md#required-parsing
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-21.md#open-question-in-required-members
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-23.md#open-questions-in-required-members
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-05-02.md#effect-of-setsrequiredmembers-on-nullable-analysis
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-11-30.md#revise-membernotnull-for-required

@333fred
Copy link
Member Author

333fred commented Jun 30, 2020

To call it out more explicitly: we know we want to do something here, but we need to find a good syntax. I'm looking forward to seeing suggestions from the community as well :).

@HaloFour
Copy link
Contributor

My vote is a keyword modifier on set/init. I kind of like required although req and must are ok, too. mustinit seems redundant.

public string Foo { get; required set; }
public string Bar { get; required init; }

@333fred
Copy link
Member Author

333fred commented Jun 30, 2020

mustinit seems redundant.

My idea there was more

public int Prop { get; must init; } 

The full word was for if it's a modifier on the property itself.

@SingleAccretion
Copy link

SingleAccretion commented Jun 30, 2020

Obligatory mention of the verbose syntax:

public not default int ReadonlyIsALongWordButItIsSomewhatClearWhatItMeans { get; init; }

I do not like it, actually, since it tells me something about the property, not that I should initialize it. My vote is with must init.

Here's some more ideas:

public int Prop { get; do init!!; }
public int Prop { get; explicit init; }
public int Prop { get; init!!; }
public int Prop { get; init else throw; }

@HaloFour
Copy link
Contributor

@333fred

The full word was for if it's a modifier on the property itself.

Got it, so it would be:

public mustinit string Foo { get; init; }

You could argue that the modifier on the property makes it easier to see which properties are required vs. having to potentially scan for the accessors. I still think I'd lean towards required or must on the setter accessor.

@Richiban
Copy link

What's the reasoning being init props being non-mandatory? I may have missed something, but I imagine that they'll be very rarely used without a req modifier.

@333fred
Copy link
Member Author

333fred commented Jun 30, 2020

Updated the proposal with a new alternative section on "Initialization Debt", which builds on this proposal as a general way of communicating what properties must be initialized in a more flexible fashion.

@333fred
Copy link
Member Author

333fred commented Jun 30, 2020

What's the reasoning being init props being non-mandatory? I may have missed something, but I imagine that they'll be very rarely used without a req modifier.

@Richiban consider the first example:

class Person
{
    string FirstName { get; init; }
    string MiddleName { get; init; }
    string LastName { get; init; }
}

Not everyone has a MiddleName (heck, not everyone even has what Americans would consider a family name either), but I still want Person to be immutable after construction.

@Richiban
Copy link

Richiban commented Jun 30, 2020

Not everyone has a MiddleName (heck, not everyone even has what Americans would consider a family name either), but I still want Person to be immutable after construction.

I get it, but why not simply let them set null explicitly?

var p = new Person 
{
    FirstName = "Alex",
    MiddleName = null,
    LastName = "Fooson"
}

It just seems like a significant weakening of a feature (or, perhaps, a requirement to develop two features) to make the case of optional properties a little easier.


EDIT: Even better: use the concept of property initial values to indicate optionality:

class Person
{
    string FirstName { get; init; }
    string MiddleName { get; init; } = null; // This is optional because it's been given a default value
    string LastName { get; init; }
}

@HaloFour
Copy link
Contributor

@Richiban

Orthogonal concerns. init accessors state that the property can only be set during initialization, not that they have to be set during initialization. And that doesn't cover set accessors that would be required during initialization but remain mutable after initialization. Splitting them into two concerns covers both bases.

@theunrepentantgeek
Copy link

Are required and initializable properties being considered as orthogonal concepts? Or is required an extra that may only be applied to initializable properties?

I'd like to be able to write property declarations that trigger a compilation failure if I fail to initialize the properties in my own constructors:

public class Person
{
    public string KnownAs { get; private required set; }
    public string FamilyName { get; private required set; }
    public string LegalName { get; private required set; }

    public Person(string knownAs, string familyName) 
    {
        KnownAs = knownAs;
        FamilyName = familyName;
        // Compiler error here because LegalName has not been initialized
    }
}

I'd find this useful for expressing intent, especially on classes that I expect will need to be modified in the future as the system develops over time.

For example, if I added

public string Honorific { get; private required set; }

to the above class, having the compiler ensure that I correctly initialized the property in every constructor (including the one in a different file, thanks to a partial class declaration) would be really useful.

@HaloFour
Copy link
Contributor

@theunrepentantgeek

Interesting scenario. I'm under the impression that this proposal covers the metadata and enforcement of consumers of the class, not of the class itself.

@333fred
Copy link
Member Author

333fred commented Jun 30, 2020

It is an interesting scenario, and one I think could be covered by the initialization debt extension. In that world, those 3 things would be part of the debt, but because they're private to your class you wouldn't be able to publish them as part of your debt contract. They would have to be assigned in the constructor.

@BreyerW
Copy link

BreyerW commented Jun 30, 2020

I like must since it flows naturally with set and init

required kind of doesnt flow (linguistically) with these two keywords but is useable on property itself not just acessor so privately if we went with keyword on property i would go with required otherwise must

@DavidArno
Copy link

DavidArno commented Jul 1, 2020

Orthogonal concerns...

Sometimes it's good to pause and think through what someone says before instantly rejecting what they are saying. When we do so, we see that the true orthogonal concerns here are init and set. They do two completely different things and the one solution is not going to fit them both very well.

I suspect I'm not alone in being completely perplexed by the opening statement "The number 1 misconception about init that we've seen is that putting init on a property will solve the issue of properties that are required to be set at object creation". The idea that this is a misconception is worrying. Since it's the no. 1 "misconception" it's clear that I'm not alone in assuming init would be mandatory.

Therefore, let's start with the requirement that it is mandatory. That's what most people will expect it to be and that's what most people will want it to be most of the time.

So how do we address @333fred's conundrum?

class Person
{
    string FirstName { get; init; }
    string MiddleName { get; init; } // many people don't have a first name so it shouldn't be mandatory
    string LastName { get; init; }
}

Well first of all, if it's not mandatory, it has no right being declared as string. It's a string? property. And we can then take the same approach as we do with non-mandatory parameters: provide a default value to make it optional (as @Richiban suggested in his edit above):

class Person
{
    string FirstName { get; init; }
    string? MiddleName { get; init; } = null;
    string LastName { get; init; }
}

Now, MiddleName is an optional initialiser property.

This feature is a nice one and it would be good to add it to set too. This is an orthogonal issues of course; set currently cannot be specified as mandatory after all. But "whilst in the area", let's address that too. And we conveniently already have a keyword that means "mandatory when used in initializers": init

For a mutable Person class where first and last names are mandatory, we then can express it as:

class Person
{
    string FirstName { get; init set; }
    string? MiddleName { get; set; }
    string LastName { get; init set; }
}

Now, init means "must be set at initialisation time". And without set it's read-only after initialisation, which therefore causes no change to what was already proposed for init.

This also then solves @theunrepentantgeek's scenario:

public class Person
{
    public string KnownAs { get; private init set; }
    public string FamilyName { get; private init set; }
    public string LegalName { get; private init set; }

    public Person(string knownAs, string familyName) 
    {
        KnownAs = knownAs;
        FamilyName = familyName;
        // Compiler error here because LegalName has not been initialized
    }
}

private init set is a bit of a mouthful, but I'm not sure how often this pattern would be used. It's likely fine for occasional use. However, if it becomes popular, we'd likely want a neat way of expressing it in a much shorter way. But that definitely is orthogonal...

@HaloFour
Copy link
Contributor

HaloFour commented Jul 1, 2020

@DavidArno

So then how do you propose to make a set accessor also mandatory?

init doesn't mean mandatory. It means you can only set during initialization. The requirement that it can only be set during initialization says nothing about whether or not you must set it during initialization. That's where this proposal comes in.

@DavidArno
Copy link

So then how do you propose to make a set accessor also mandatory?

As explained above, by using init set.

init doesn't mean mandatory. It means you can only set during initialization

As clearly stated by the OP, many (likely most) people assume and will continue to assume it does mean mandatory because it makes sense for it to be mandatory for most cases. So I've turned @333fred's proposal on its head with my counter proposal which allows it to be mandatory and meets all the others requirements mentioned in this thread all without adding an extra keyword that has to be used in most scenarios and is only omitted in exceptional cases.

@Richiban
Copy link

Richiban commented Jul 1, 2020

I admit in my first post that I never considered the case of required but mutable properties, but I would still argue for the language design philosophy of:

the shortest code gets you the strictest behaviour

in the name of safety and having a language that will cause developers to fall into the pit of success.

@HaloFour
Copy link
Contributor

HaloFour commented Jul 1, 2020

@DavidArno

Nothing about init implies that it's required. You seem to be reading that into the keyword and I doubt that will be the common interpretation. In any case your interpretation of init is not going to happen by C# 9.0 and init isn't likely to be pushed given that records depends on it.

@YairHalberstadt
Copy link
Contributor

A few points:

  1. What about fields?
  2. Whether a property is required may depend on which constructor is used. For example it's not an uncommon pattern to have a default constructor, in which case all properties are required, ans a constructor which sets everything, in which case none are.
  3. All public fields of non-nullable reference types should be required when you use a default constructor on a struct, as should auto properties. I don't think there's any good solutions to private fields in a struct.

@333fred
Copy link
Member Author

333fred commented Jul 1, 2020

Therefore, let's start with the requirement that it is mandatory. That's what most people will expect it to be and that's what most people will want it to be most of the time.

@DavidArno but that's not what it is. Unfortunately, properties from C# 1.0 were optional by default, and so that's the world we're going to have to live with. In fact, other than conflating init and req, your proposal ends up in a very similar place to the original, except that it introduces massive breaking changes to the .NET ecosystem.

Well first of all, if it's not mandatory, it has no right being declared as string. It's a string? property.

Nullable was not enabled in that code snippet, it cannot be string?. You'll note that my original example did have nullable enabled, and the property was string? in that example. Any proposal we come up with here is going to have to live in this world. Further, being mandatory has nothing to do with the type of the property. It could be perfectly reasonable to default the property to string.Empty for cases without a middle name.

Your proposal paints a nice picture, one I even like. However, it would require changes to large swaths of code, and if we were going to do that I don't think it goes far enough. I'd much rather extend the concept of definite assignment to all properties of an object, and have constructors/factory methods export explicit contracts of "these things must be initialized before this object is considered valid". Then we could have a world like this:

#nullable enable
// Maybe the contract on a default constructor could be implicit, but I'm just putting it on the
// class declaration for example's sake
[RequiresInit(nameof(Person.FirstName), nameof(Person.LastName)]
public class Person
{
    string FirstName { get; init; }
    string? MiddleName { get; init; } = null;
    string LastName { get; set; }
}

public static class PersonFactory
{
    [RequiresInit(nameof(Person.LastName)]
    public static Person MakeMads() => new Person { FirstName = "Mads" };
}

var kristensen = PersonFactory.MakeMads() { LastName = "Kristensen" };
var torgersen = PersonFactory.MakeMads() { LastName = "Torgersen" };
var error = PersonFactory.MakeMads(); // Error: "LastName" is uninitialized"

In this world of mutable last names, no extra keyword would be required here: you didn't initialize the property, so it's part of the "debt" that is exposed from a constructor or factory method. But as much as I like this system, it presumes that properties are required by default, which is unfortunately just not true today, and not likely something that we will be able to change.

the shortest code gets you the strictest behaviour

What we do in the name of backcompat, @Richiban. What we do in the name of backcompat.

@333fred
Copy link
Member Author

333fred commented Jul 1, 2020

What about fields?

The concept of initialization debt could certainly be extended to fields as well.

Whether a property is required may depend on which constructor is used. For example it's not an uncommon pattern to have a default constructor, in which case all properties are required, ans a constructor which sets everything, in which case none are.

That's what the more general initialization debt extension would cover.

All public fields of non-nullable reference types should be required when you use a default constructor on a struct, as should auto properties. I don't think there's any good solutions to private fields in a struct.

Until we get non-defaultable structs I think this is a pretty moot point. It's something we certainly want, but without the feature it doesn't matter.

@dersia
Copy link

dersia commented Jul 2, 2020

I still don't understand how init is not required.
Looking at @DavidArno and @Richiban comments and the replies by @333fred made me think about what is being said here.

Please tell me how can something been ONLY setable during initialization, but then not be mandetory. That property is not setable after, so what is the value going to be? Is it default? Is it null?

In this case I agree with what has been set, the property/field needs to be either nullable or need to be preinitialized (not sure if this makes even sense for something that is called init).

Are we really talking about introducing a keyword or attribute just so the developer doesn't have to initialize during initialization?

I rather write

var person = new Person 
{
  Firstname = "John", 
  Middlename = default, 
  Lastname = "Doe" 
}

over introducing a new keyword or Attribute.
And also I like that I am forced to initialize the properties and don't forget something, especially when the class is altered afterwards.

Still don't understand what the state of properties will be if they are not initialized even though they can only be set during initialization 🤔

Edit: autocorrection

@333fred
Copy link
Member Author

333fred commented Jul 2, 2020

Still don't understand what the state of properties will be if they are not initialized even though they can only be set during initialization 🤔

It's going to be whatever default was provided, or default(T) if none was provided.

Please tell me how can something been ONLY setable during initialization

Don't forget that initialization includes field/property initializers (including property init bodies) and constructors. Lots of places for a default to be set.

@miloush
Copy link

miloush commented Jul 4, 2020

Do I understand correctly that C# 9 init for properties should have the same function as readonly for fields? Why have two different keywords for the same purpose?

As for this issue, if both { get; init; } and { get; set; } are valid, I would expect the former one to be required at initialization but impossible to set afterwards (expressed by the lack of set). If I wanted to enforce initialization and allow changing it later, { get; init; set; } would work for me.

Do we expect this to be a commonly used feature? Because an attribute on the property might do the job as well...

@HaloFour
Copy link
Contributor

HaloFour commented Jul 4, 2020

@miloush

init is only like readonly in that a consumer can only invoke it during construction (although not necessarily during the constructor), but it doesn't prevent the underlying field from being mutated later.

init as it is expected to ship in C# 9.0 doesn't require initialization, hence this proposal which seeks to add the ability to require initialization to init properties, set properties and fields.

@HaloFour
Copy link
Contributor

@KennethHoff

See: #6780

@chrisoverzero
Copy link

chrisoverzero commented Dec 30, 2022

@KennethHoff

I think you have an impedance mismatch. You’re trying to put required-ness into a positional record. But for a positional record, positional parameters are constructor parameters – required by default. To get record semantics with property initializers, elide the primary constructor. (But since you’re using a readonly struct, are you sure you don’t want to use the positional parameters anyway?)

The short answer is that your parameters seem non-required because you’re using a value type. Consider using a reference type – record class.

@TahirAhmadov
Copy link

@KennethHoff I think the underlying issue here is that all structs can be "created" (it's actually a zero-set) using something which "looks like" a default parameterless ctor, including records. This is coming from C#1 when there was no default keyword and doing new Struct() was the syntax to do what is essentially default(Struct). I think we should phase it out, but it's obviously an expensive breaking change and I understand why LDT is hesitant to do it.

@KennethHoff
Copy link

KennethHoff commented Dec 31, 2022

@HaloFour I see...

@chrisoverzero

But since you’re using a readonly struct, are you sure you don’t want to use the positional parameters anyway?

I don't like constructors as it's difficult to know what I'm actually assigning - is the 3rd parameter email or password? etc.. (Primitive Obsession notwithstanding..)
I was under the impression that only the properties specified inside the parameters were part of the "record-ness" of a type, but after writing my post I did some more research, and it seems all properties are apart of it? By that I mean; If I simply remove the primary constructor of the record struct in the image, I would have the same functionality (value equality etc..), but without the weird #6780 issue?

@TahirAhmadov I knew of that actually, but didn't consider in record-struct land

@thomaseyde
Copy link

thomaseyde commented Dec 31, 2022 via email

@CyrusNajmabadi
Copy link
Member

@thomaseyde can you edit your post? It's full of email goop. I'd do it, but I'm genuinely not sure what parts to keep.

@peter-dolkens
Copy link
Contributor

peter-dolkens commented Jan 3, 2023

I know I'm coming in late to this, but do we need to consider types with multiple constructors?

public class Person
{
	public String FirstName { get; }
	public String MiddleName { get; }
	public String LastName { get; }
	public Int32 Age { get; set; }
	
	public Person(String firstName, String lastName)
	{
		FirstName = firstName;
		LastName = lastName;
	}
	
	public Person(String firstName, String middleName, String lastName)
	{
		FirstName = firstName;
		MiddleName = middleName;
		LastName = lastName;
	}
}

This clearly states that we either need to provide 2 or 3 properties when initializing (via constructor).

We can then already use very similar initialization code to access these constructors like so:

var person = new Person(
	firstName: "hello",
	lastName: "world")
{
	Age = 21
};

It feels like we're trying to make this slightly neater at the end of the day - so something like this might work:

var person = new Person
{
	@firstName = "hello",
	@lastName = "world",
	Age = 21
}; // Maps explicitly to the (String firstName, String lastName) constructor

I used @ here to indicate constructor argument, not an initializer property, but I'm sure a better alternative could be proposed.

This allows us the flexibility of explicitly defining the allowed combinations of required/optional parameters through the definition of constructors, as we do already.

Personally, I like the idea of a required attribute/keyword on properties, because I hate constructors - but I do recognize that they have features that can't be achieved with pure properties as things stand today.

@MortenMeisler
Copy link

I know I'm coming in late to this, but do we need to consider types with multiple constructors?

public class Person

{

	public String FirstName { get; }

	public String MiddleName { get; }

	public String LastName { get; }

	public Int32 Age { get; set; }

	

	public Person(String firstName, String lastName)

	{

		FirstName = firstName;

		LastName = lastName;

	}

	

	public Person(String firstName, String middleName, String lastName)

	{

		FirstName = firstName;

		MiddleName = middleName;

		LastName = lastName;

	}

}

This clearly states that we either need to provide 2 or 3 properties when initializing (via constructor).

We can then already use very similar initialization code to access these constructors like so:

var person = new Person(

	firstName: "hello",

	lastName: "world")

{

	Age = 21

};

It feels like we're trying to make this slightly neater at the end of the day - so something like this might work:

var person = new Person

{

	@firstName = "hello",

	@lastName = "world",

	Age = 21

}; // Maps explicitly to the (String firstName, String lastName) constructor

I used @ here to indicate constructor argument, not an initializer property, but I'm sure a better alternative could be proposed.

This allows us the flexibility of explicitly defining the allowed combinations of required/optional parameters through the definition of constructors, as we do already.

Personally, I like the idea of a required attribute/keyword on properties, because I hate constructors - but I do recognize that they have features that can't be achieved with pure properties as things stand today.

Good point. Although I think your example shows that the MiddleName is in fact not required, hence there is no need for the required keyword. If the goal is to make it immutable and avoid constructors, the best option is to make MiddleName init-only.

@peter-dolkens
Copy link
Contributor

Although I think your example shows that the MiddleName is in fact not required, hence there is no need for the required keyword. If the goal is to make it immutable and avoid constructors, the best option is to make MiddleName init-only.

DISCLAIMER: Example for demonstration purposes only 😉

Was the simplest example I could come up with that demonstrated the syntax.

@333fred 333fred added the Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification label Jan 9, 2023
@jods4
Copy link

jods4 commented Jan 12, 2023

I don't think anyone mentioned this before, apologies if I missed it in the many comments that are in this issue.

I encountered an unfortunate consequences of the new required keyword.
Inside Expression<>, sometimes MemberInitExpression is used for purposes other than initializing a new instance.
This is not semantically correct, but it's convenient.

For example, linq2db supports writing the SET part of an UPDATE sql statement like so:

class Product
{
  public required int Id;
  public required string Name;
}

db.Products
  .Where(p => p.Id == 3)
  .Update(_ => new() { Name = "John" });  // CS9035
  
// UPDATE Product SET Name = 'John' WHERE id = 3

This is not the only syntax linq2db provides, but it's a very friendly and convenient one when you want to set many different properties at once.

Of course, the problem is that C# complains that the initializer doesn't set Id, which is required.
To make it slightly worse you can't #pragma disable it since it's an error, not a warning (not that having to pragma every code like this would be nice).

I wonder if CS9035 should be muted inside Expressions. This is just one example, but one can do lots of creative things with Expressions, e.g. initializing some members magically.

I can't remember exactly what, but I recall there was another C# error/feature that is disabled inside Expressions, for similar reasons?

PS: it would be incredibly nice if the new with { ... } expression could be applied to regular object and supported inside Expression. It could be used for similar scenarios and that would be more semantically correct.

@Eli-Black-Work
Copy link

@jods4 This exact problem was recently mentioned in the Entity Framework Core repository, too. There was a dedicated topic for it at dotnet/efcore#28557

Other relevant comments:

AFAIK, this issue was never solved, so I've posted a comment nagging @roji about it 🙂 (dotnet/efcore#795 (comment))

@HaloFour
Copy link
Contributor

@jods4

I've always thought it was weird that EF used new expressions for updates like that, but obviously that was the best choice given all of the other limitations with expression trees in the language. I do agree that it would seem that with would be the better option here, although that would require allowing evolution of expression trees to support newer language features as well as supporting a "wither" pattern for non-record types.

@stevendarby
Copy link

stevendarby commented Jan 13, 2023

@Bosch-Eli-Black @HaloFour EF Core doesn't use new expressions for direct updates. It uses a SetProperty method: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#basic-executeupdate-examples

@roji
Copy link
Member

roji commented Jan 13, 2023

@Bosch-Eli-Black re EF Core:

AFAIK, this issue was never solved, so I've posted a comment nagging @roji about it slightly_smiling_face (dotnet/efcore#795 (comment))

Sure it was - we simply decided not to use new { ... } to express updates; apart from the issue with required properties, this also had other problems (e.g. how to reference EF shadow properties, which don't have a CLR property). At the end of the day, while it's tempting to use new { ... } to express updates, it's ultimately a bad idea since nothing new is being created/instantiated.

More on this in dotnet/efcore#795 (comment).

@jods4
Copy link

jods4 commented Jan 13, 2023

I hope my comment doesn't get discarded because EF Core opted for a different solution.
It's great that EF is happy where it landed but I hope C# would still consider that:

  1. Other libraries have had similar usage for a long time.
    I cited linq2db, which has had both .Set() (EF Core-like) and .Update(_ => new { }) for years.
    As EF Core noted, many users are going to adopt required in their models and it will nix this API.
    There are alternatives, but the initializer syntax was really convenient and it's sad we have to choose between exclusive options., which is kind of breaking compatibility with a part of the existing ecosystem.

  2. The argument that nothing is created so it shouldn't be a new is real (with would be awesome) but it's dismissing that other more legitimate use-cases can be made. Once Expressions and Reflection are in the picture, anything can happen.
    For example, I could create an API that builds a new object from an Expression, but fills all missing properties with some default values from a "template/default" instance. It would still look the same and be subject to the same requiredness issues.

@tats-u
Copy link

tats-u commented Feb 14, 2023

The current implementation (.NET 7) has a problem and disappoints me a lot; it doesn't allow such a code like:

// Error in this line
Console.WriteLine(new Foo().Bar);

public class Foo {
    public required string Bar { get; init; }

    Foo() {
        // I want to make an assignment only when the caller is not a form like `new Foo { Bar = "some string" }` somehow
        Bar ??= "Hello world!";
    }
}

I want constructors to be able to initialize properties with both required and init. If they could, = null!; for property initialization would be unnecessary, and null wouldn't contaminate properties of non-nullable types anymore.

I just wish all nulls in properties and fields of non-nullable types would be wiped out of all maintained .NET projects all over the world.

@KieranDevvs
Copy link

I just wish all nulls in properties and fields of non-nullable types would be wiped out of all maintained .NET projects all over the world.

That's just never going to happen. It would break almost every C# program in existence, prior to NRT's.

@TahirAhmadov
Copy link

@tats-u I think what you want is a property which has a default value, but can be set in init block to set a custom value. In this case, you don't really need required. Also, you don't need compound assignment in the ctor - just do Bar = "Hello world!";.

Also, I think you expect the ctor to be executed after the init block, which is not the case - the init block is executed after the ctor. If you want to make changes after the init block is executed, there is #6591, which proposes "final initializers".

@tats-u
Copy link

tats-u commented Feb 14, 2023

@KieranDevvs That's why I chose "wish" instead of "hope". It seems much more difficult than I thought though.
@TahirAhmadov I'd like to initialize the value of Bar according to the arguments of the constructor. I chose the simplest example without arguments or extra processing in the constructor.

Foo(/* some arguments */) {
    // or an alternative expression to detect if the property `Bar` has not been set yet
    if (Bar == null)
    {
        // complex processing according to the arguments
        Bar = /* complex expression according to the arguments */;
    }
}

@TahirAhmadov
Copy link

@tats-u but you don't need if(Bar == null) - it will always be null in your example.

@tats-u
Copy link

tats-u commented Feb 14, 2023

@TahirAhmadov This will be a non-null value if you call the constructor by new Foo { Bar = "Baz" }. The current specification of C# doesn't seem to tell whether the property Bar has been passed in the new expression without breaking the rule null shouldn't be contained in non-nullable types because Bar is the non-nullable type and shoudn't be null.

@HaloFour
Copy link
Contributor

@tats-u

This will be a non-null value if you call the constructor by new Foo { Bar = "Baz" }

Bar will always be null in the constructor there. That code is the equivalent to:

var $temp = new Foo();
$temp.Bar = "Baz";
var foo = $temp;

The constructor is always invoked first, property initializers always come second.

@tats-u
Copy link

tats-u commented Feb 14, 2023

@HaloFour Do you mean there's no way for constructors to tell whether the initial property values has been passed in the initializers, right?
I wonder if I have to use private field values for default values and accept the cost of computing the default value according to arguments. Update: I tried it in https://dotnetfiddle.net/ and fixed; I had to wrap a field in a property.

private string bar;
public string Bar { get { return bar; } init { bar = value; } }

Foo (/* args */) {
  bar = /* computed values */;
}

@HaloFour
Copy link
Contributor

@tats-u

Do you mean there's no way for constructors to tell whether the initial property values has been passed in the initializers, right?

Correct. Initializers are mostly just syntax candy for assigning values to the property setters immediately after the constructor is called. With init properties the C# language also enforces that the property setter can only be invoked within the context of an initializer expression, but otherwise they are a normal property setter.

@acaly
Copy link
Contributor

acaly commented Feb 15, 2023

One reason I would like to vote for method calls in initializers is this-sometimes we want to do some post-initialization work after properties are set. For example:

var foo = new Foo()
{
    Bar = "Baz",
    Init(),
};

We can have fields/properties and recently events initialization in initializer. For completeness, we should support methods, though I can imagine that there might be some design work related to the required and interactions with other required properties, for example.

@tats-u
Copy link

tats-u commented Feb 15, 2023

@HaloFour I found fields & constructors and properties and initializers are independent when {get; init; } is used for properties.
Its design looks not identical for me... Anyway thank you for your answers.

@bugproof
Copy link

bugproof commented Feb 19, 2023

It would be cool if something like this:

public record Foo(string Bar) could alternatively be initialized like this new Foo { Bar = "bar" } now that we can have required init only properties or make it possible to add required in the primary constructor public record Foo(required string Bar)...

Now we have 2 ways of initializing things and which way to choose???

  1. public record Foo(string Bar)
  2. public record Foo { public required string Bar { get; init; } }

Why can't 1 work also like 2? I prefer how clean 1st way is and I also prefer to initialize longer things without constructor.

image
image

It's a bit dumb that these things don't work interchangeably, the compiler could at least allow using object initializer for stuff in constructors and compile that just like it was passed to a constructor. Someone should make D# lol...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion Proposal
Projects
None yet
Development

No branches or pull requests