csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

Champion "readonly for locals and parameters"

Open gafter opened this issue 7 years ago • 1083 comments

  • [ ] Proposal added
  • [ ] Discussed in LDM
  • [ ] Decision in LDM
  • [ ] Finalized (done, rejected, inactive)
  • [ ] Spec'ed

See also https://github.com/dotnet/roslyn/issues/115

Design review

  • https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-27.md#readonly-modifiers-for-primary-constructors
  • https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-17.md#readonly-parameters
  • https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-26.md
  • https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-31.md
  • https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-08.md#readonly-for-primary-constructor-parameters

gafter avatar Feb 26 '17 19:02 gafter

Any plan for readonly types(classes and structs)?

gulshan avatar Mar 23 '17 08:03 gulshan

should support

readonly i = 0; // shorthand for readonly var
const j = 0; // shorthand for const var

Thaina avatar Mar 27 '17 08:03 Thaina

Wasn't val going to be short for readonly var?

jnm2 avatar Mar 27 '17 14:03 jnm2

@jnm2 I just don't like the idea of adding new keyword. Especially it is already have keyword in the language that has the same meaning

readonly might be a bit longer but we already preserved it from the start. We should reuse it. And to be shorter, just let we use readonly without var

At least I have seen some suggestion to use let that would still better than val because we already have it as keyword, even for linq scope

Especially because val was not keyword. I really have my code var val = 0; and I bet there are many people have val as variable or field name in their code like me. I think val is a bad choice

Thaina avatar Mar 28 '17 01:03 Thaina

@Thaina Yes, I'm inclined to agree.

jnm2 avatar Mar 28 '17 02:03 jnm2

@Thaina

Especially because val was not keyword. I really have my code var val = 0; and I bet there are many people have val as variable or field name in their code like me. I think val is a bad choice

And you could continue to. Like var, val would be a contextual keyword, in that it only behaves like the keyword when it doesn't make sense for it to behave like anything else. So var val = 0; would remain perfectly legal.

Although I do prefer let to val, mostly because I think it looks sufficiently different.

HaloFour avatar Mar 28 '17 02:03 HaloFour

Oh yes! let was the one I liked. Thanks!

jnm2 avatar Mar 28 '17 02:03 jnm2

@HaloFour It not breaking change I understand but it still ambiguous

BTW I still don't like let. I prefer readonly. But at least let is better than val

Thaina avatar Mar 28 '17 02:03 Thaina

let is a bit early basic; also was read write. Not sure how let implies readonly?

benaadams avatar Mar 28 '17 02:03 benaadams

@benaadams

let is a bit early basic; also was read write. Not sure how let implies readonly?

let is also F# where it is the readonly (by default) binding of an identifier. let is also C# LINQ where it is the declaration of a readonly range variable.

Personally the latter reason is enough for me. It's already a contextual keyword, and in that existing context it creates a readonly identifier.

HaloFour avatar Mar 28 '17 02:03 HaloFour

let is also C# LINQ where it is the declaration of a readonly range variable.

Fair enough 😄

benaadams avatar Mar 28 '17 02:03 benaadams

@gulshan

Any plan for readonly types(classes and structs)?

Do you mean immutable types? If so, that's a completely separate proposal (I'm pretty sure it's been made before).

Richiban avatar Mar 30 '17 13:03 Richiban

ITNOA

@Richiban where I can found it?

soroshsabz avatar Mar 30 '17 14:03 soroshsabz

@soroshsabz

ITNOA

That's a new one!

https://github.com/dotnet/roslyn/issues/7626 and https://github.com/dotnet/roslyn/issues/159 are probably what you're looking for.

Richiban avatar Mar 30 '17 14:03 Richiban

Something I like about final locals in Java is that it's possible to declare one as not assigned and then have branching logic to assign it. The Java compiler uses flow analysis to ensure that for each branch that the local is assigned exactly once.

final String name;
if (entity instanceof Person) {
    Person person = (Person)person;
    name = person.getFirstName() + " " + person.getLastName();
}
else if (entity instanceof Company) {
    Company company = (Company)company;
    name = company.getFirmName();
}

This can be useful in those scenarios where you want the local to be readonly, the expression to calculate it can throw and you want the scope of the local to exist beyond a try block.

HaloFour avatar Mar 30 '17 14:03 HaloFour

@Richiban thanks, but I hope to see comprehensive proposal about immutable object in csharplang project.

soroshsabz avatar Mar 30 '17 14:03 soroshsabz

@HaloFour Is conditional operator ( ?: ) not sufficient for this purpose?

soroshsabz avatar Mar 30 '17 14:03 soroshsabz

@soroshsabz

Sometimes not. I amended my comment to mention try/catch scenarios where C# offers no single expression. You could extract the logic to a separate function but that's more verbose ceremony. Even if it could be expressed as a single conditional expression sometimes it's more readable expanded out into multiple statements.

Either way, Java supports this, and I make use of it frequently enough that I think it would be useful here.

HaloFour avatar Mar 30 '17 14:03 HaloFour

I think simple rule like "All local readonly variables must be initializing immediately after declaration." cause to improve simplicity and readability for programmers. In Java case some programmers maybe surprise to final variable has not initialize and must be track code to find the where is this variable initialize?

soroshsabz avatar Mar 30 '17 14:03 soroshsabz

@HaloFour,

That's yet another use-case for match:

let name = entity match (
    case Person person : $"{person.FirstName} {person.LastName}",
    case Company company : company.FirmName
);

DavidArno avatar Mar 30 '17 14:03 DavidArno

@soroshsabz

We may differ on opinion there. If the expression has to be overly complex in order to satisfy an overly strict language feature that only decreases overall maintainability and readability. I'd rather the flow be logical and the compiler enforce readonly-ness where appropriate.

And as a Java programmer who works directly with hundreds of other Java programmers I can say that this has never been a source of confusion. It's a pattern used fairly frequently across the Java ecosystem. If anything I think I would find it much more annoying that I couldn't declare and assign a readonly variable like this.

@DavidArno

It's just one exceptionally simple case. match won't handle the exception handling scenario. And again, forcing the developer to try to pack it all into a one-liner, or to extract that local logic elsewhere, does not improve the readability or maintainability of the program.

HaloFour avatar Mar 30 '17 14:03 HaloFour

In C# we actually have elaborate flow analysis for struct that it must be set all field properly if we not call the constructor, else it will cause compile error

I agree that readonly should do the same

Thaina avatar Mar 30 '17 15:03 Thaina

I've always liked the idea that readonly can be applied to local and parameter declarations, e.g.:

public void MyMethod(readonly int arg) //...

and

void Main()
{
	readonly string[] items = new [] {"one", "two"};
}

with the added feature that let can be used a shorthand for readonly var:

void Main()
{
	let items = new [] {"one", "two"};
}

However, you could allow the splitting of declaration and assignment if you write it out in full. The existing definite assignment analysis can be used to make sure that the variable is never overwritten:

void Main()
{
	readonly string name;

	try {
		name = MakeWebRequest("http://my.resource/name");
	}
	catch (Exception) {
		name = null;
	}

	// Here name is definitely assigned, and now cannot not be written to (Compiler error)
}

But what happens if the variable is perhaps not assigned? Can we still write to it?

void Main()
{
	readonly string name;

	if(person != null)
        {
		name = person.Name
        }

	name = "Bob"; // Is this allowed?
}

Richiban avatar Mar 30 '17 15:03 Richiban

@Richiban No you must

if(person != null)
    name = person.Name;
else name = "Bob"; // must else

instead

Thaina avatar Mar 30 '17 15:03 Thaina

By the way, I was prefer my idea of returnable block #249 instead of using flow analysis as that

local readonly is one of my reason to make #249 but I forgot to mention it

Thaina avatar Mar 30 '17 15:03 Thaina

@Richiban It is unlikely that we would consider adopting something analogous to Java's "blank final" variables or definite unassignment rules. A readonly local would have to be initialized at the point where it is declared.

gafter avatar Apr 04 '17 17:04 gafter

I understand the desire to support try/catch, but I'm having a hard time envisioning how that would work. Consider:

readonly int x;
try {
    DoSomething();
    x = 1;
    DoSomethingElse();
}
catch {
   x = 0;
}

We can see that x is definitely assigned. However, the value of x could be assigned twice depending on where the exception occurs.

Grauenwolf avatar Apr 07 '17 09:04 Grauenwolf

@Grauenwolf I agree. It makes much more sense that readonly locals insist on being declared and assigned in the same statement, much like var is today.

Richiban avatar Apr 07 '17 09:04 Richiban

To be clear, I would prefer that readonly int x doesn't require immediate assignment. I just don't know if it is possible for this use case.

Grauenwolf avatar Apr 07 '17 09:04 Grauenwolf

@Grauenwolf

In Java that code would be a compiler error, specifically because x isn't definitely assigned exactly once. The following would be legal:

final int x;
try {
    x = CalculateX();
}
catch (Throwable e) {
    x = 123;
}

The following would not:

final int x;
try {
    x = CalculateX();
    DoSomethingElse();
}
catch (Throwable e) {
    x = 123;
}

HaloFour avatar Apr 07 '17 10:04 HaloFour