-
Notifications
You must be signed in to change notification settings - Fork 4k
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: 'readonly' for Locals and Parameters #115
Comments
I'd like to allow the readonly class Point
{
public int x; // Implicit readonly
public int y; // Implicit readonly
Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
Point origin = new Point(0, 0);
origin.x = 1; // Error: can't reassign a readonly class member |
class C
{
private int _i;
public int Read() readonly { return _i; } // method has 'readonly' modifier
public void Write() { _i++; }
}
readonly C c = new C();
c.Read();
c.Write(); // compile error: method is not readonly The As functional programming paradigms become more commonplace, function purity becomes an comforting property to have, and an appealing property to enforce. Today we have void PureFunction(C c) pure // method has the 'pure' modifier
{
c.Read();
c.Write(); // compile error: a pure function's parameters are implicitly readonly
} Framework code could optimise for or even require pure functions. The compiler could automatically hoist loop invariant code. |
Re parameters, I don't like the way that For structs, it would be nice to have those semantics - I've often wished for |
@drewnoakes I completely agree, but backwards compatibility means we're screwed. readonly var list = new ArrayList<int>();
readonly var size = list.size(); // Error, calling non-readonly method on readonly reference. |
@stephentoub The following example is ambigous:
The first thing that I thought, when I saw My proposal to resolve this ambiguity is: public void Use(readonly ref const Matrix3D matrix)
{
…
matrix = new Matrix3D(); // Error: can't assign to readonly reference
matrix.M31 = 0; // Error: can’t change immutable value
…
} The variable is readonly and the public void Use(ref const Matrix3D matrix)
{
…
matrix = new Matrix3D(); // OK
matrix.M31 = 0; // Error: can’t change immutable value
…
} Calling methods on
|
@ryancerium, I suppose this feature would require new metadata in the type system, therefore a new CLR and at the same time a new BCL. |
@ryancerium can you further explore why the readonly Point origin = new Point(0, 0);
origin.x = 1; // compile error: can't modify a readonly instance's fields |
@drewnoakes I want to co-opt Here's a different Point class that has mutable x and y values. class Point
{
public int x, y;
}
class C
{
readonly Point origin = new Point(0, 0);
public void M(readonly Point p)
{
origin.x = 100; // Totally fine in C# as-is today.
p.x = 100; // Error per your suggestion, seems weird when I can modify origin
readonly Point d = new Point(p.x - origin.x, p.y - origin.y);
d.y = 100; // Error per your suggestion, seems weird when I can modify origin
// d = new Point(-1, -1); // Illegal today, reassigning the reference
}
} |
Agreed on the desire for readonly classes, it would be very much appreciated. I think the syntax for local readonly values should take a leaf out of the F# book and use
Compared to:
I think the second version is much more 'scannable'. |
The devil is always in the details on these kinds of proposals around immutability, const, etc. (i.e. when you start working through the intricacies, there are a variety of interesting gotchas and corner cases that need to be handled and increase complexity or at least acknowledged that there are holes.) I have several more proposals to post in this area, I just didn't get a chance to do so yesterday; I'll ensure they're linked to from here when I put them up, hopefully later today. |
As somebody who also programs in Java which has |
@stephentoub Ain't that the truth. Is there a (very large) matrix anywhere that combines the list of types and methods and object locations (parameter/field/local/static)? Or do the language designers just keep them in the back of their head at all times? struct S{}
struct<T> where T : struct GVS{}
struct<T> where T : class GRS{}
class C{}
class<T> where T : struct GVC{}
class<T> where T: class GRC{}
dynamic D{}
static class
{
int a;
S Ah forget it, I'm getting tired already and realizing that the list is longer than War and Peace. |
I like this proposal, especially around using |
I fully agree that Of course, let would work with type inference; you could simply swap This also has the added benefit of matching both F# and Swift for syntax. |
@Richiban: However, I also agree that |
@axel-habermaier Thanks, I was aware of the correct F# syntax. Perhaps I wasn't clear - |
The "let" keyword would not read well for parameters. |
Is it possible for the compiler to determine that a struct is not modified in a method? If so, the struct could then be passed by ref to that method as a compile-time optimization. Obviously a |
How do you mean? |
My two cents? Support The behavior of this modifier will be exactly like the behavior of the public class Box<T> {
public T Value;
}
public struct Incrementor {
private int value;
public Incrementor(int initialValue) {
this.value = initialValue;
}
public int Increment() {
this.value += 1;
return this.value;
}
}
public void Foo(Incrementor i1, readonly Incrementor i2, readonly Box<string> boxed) {
Debug.Assert(i1.Increment() == 1);
Debug.Assert(i1.Increment() == 2);
Debug.Assert(i1.Increment() == 3);
Debug.Assert(i2.Increment() == 1);
Debug.Assert(i2.Increment() == 1);
Debug.Assert(i2.Increment() == 1);
readonly Incrementor i3 = new Incrementor(0);
Debug.Assert(i3.Increment() == 1);
Debug.Assert(i3.Increment() == 1);
Debug.Assert(i3.Increment() == 1);
i3 = new Incrementor(5); // compiler error, i3 is readonly
boxed.Value = "foo";
Debug.Assert(boxed.Value == "foo");
boxed.Value = "bar";
Debug.Assert(boxed.Value == "bar");
boxed = new Box<string>(); // compiler error, boxed is readonly
readonly int number = int.Parse("4");
int.TryParse("5", out number); // compiler error, number is readonly
}
Foo(new Incrementor(0), new Incrementor(0), new Box<string>() { Value = "baz" }); Additionally, as a counterpart to var i = 2;
let j = 3;
i = 4;
j = 5; // compiler error, j is readonly Apart from the existing similar usage of |
We definitely need readonly locals/params if we want C# to be considered as practical language with functional programming concepts. Even Java gives you this possibility with Is it planned for C# 8? |
@aleks-sidorenko |
What does this really give us? Besides piece of mind of course. Don't get me wrong, I like the idea from a self-documenting code perspective. But is there any performance or API design advantages? -----Original Message----- @aleks-sidorenko |
@Grauenwolf no it's not about API design, parameters, with the exception of |
The proposal is not 100% clear on what it means for the calling code. I think it is quite important that ref should not be prefixed when calling the function. My understanding of having to prefix ref is to make it clear that the value might get mutated, which is an important thing to know. In the readonly ref case that is guaranteed not to happen, and it would actually make it unclear if the value is being mutated or not. I think it is also quite important to support operator overloads with readonly ref. So I propose this:
Some reasons for why I believe this is extremely important for C#'s future. At Unity we are very interested in seeing C# develop into a high performance language. We very much want to see this happen as soon as possible. In Unity (using mono), ByValue takes 17.3ms while ByRef takes 3.5ms. In Unity and in game development in general the below is very real world common code that needs to be fast. We are using a lot of structs because for games hitting 60 or 90FPS it is important that our customers can optimize their game to have zero GC allocations during a frame. This implies heavy usage of structs. And as you can see from the example below, having to prefix with ref looks quite ugly when doing math.
|
@joeante ref keyword is not only for mark muted in method parameter, another usage of |
I agree with @joeante that specifying |
Whoa there. How are |
@whoisj I never said it was. I said "at the call site". Having to specify I would much rather see: var vectorC = vectorA + vectorB;
var dot = Vector.Dot( vectorA, vectorB ); than: var vectorC = Vector.Add( readonly ref vectorA, readonly ref vectorB );
var dot = Vector.Dot( readonly ref vectorA, readonly ref vectorB ); As was mentioned, most of the math/physics related code in a game engine would likely be written to use |
@Ziflin You right, so I think we have agreed with If any person have question about why a method writer declare input parameter If asked again why big object declare as According to above reason I want to can declare such method as below: public void Add(readonly ref Vector vectorA, readonly ref Vector vectorB)
{
...
} |
@joeante @Ziflin @soroshsabz shouldn’t pass by ref optimization just be something that the JITter does when it sees that the called method never tries to write to the struct and it’s |
@binki If JITter always does that work we would never request this feature. Because it never do its job as much as we want that why we need explicit syntax to force it to do Not to mention making contract like virtual or interface |
@Thaina I’m not sure what the current calling convention is, but a struct passed by value could safely be treated as COW by the synchronous parts of the callee. Such an optimization would work fine with interfaces too. |
I’m just concerned that this issue is being hijacked by all these ideas about const correctness that are unrelated to it that will distract from the simple change of adding Now, this does incidentally enable So can we keep the scope of this narrow? ;-) |
This is now tracked at dotnet/csharplang#188 |
I think |
@TahirAhmadov I agree with you that defining I do have an issue with your suggestion of However, a shorthand for This issue is dead though…Please see dotnet/csharplang#188 and keep the discussion there. There has already been some discussion over |
(Note: this proposal was briefly discussed in #98, the C# design notes for Jan 21, 2015. It has not been updated based on the discussion that's already occurred on that thread.)
Background
Today, the ‘readonly’ keyword can be applied to fields; this has the effect of ensuring that a field can only be written to during construction (static construction in the case of a static field, or instance construction in the case of an instance field), which helps developers avoid mistakes by accidentally overwriting state which should not be modified. Optimizations are also possible in a compiler based on the knowledge that the field is immutable after construction.
Here’s an example of a class defined with a readonly field. The ‘m_birthDay’ field is explicitly declared by the developer to be readonly, which means any attempt to set it after construction will cause a compile-time error. The ‘FirstName’ and ‘LastName’ properties, which are defined with getter-only auto-props (introduced in C# 6), also result in readonly fields generated implicitly by the compiler, since there’s no need for a field to be writable if there’s no way for code to set it.
Problem
Fields aren’t the only places developers want to ensure that values aren’t mutated. In particular, it’s common to create a local variable to store temporary state, and accidentally updating that temporary state can result in erroneous calculations and other such bugs, especially when such "locals" are captured in lambdas.
Solution: readonly locals
Locals should be annotatable as ‘readonly’ as well, with the compiler ensuring that they’re only set at the time of declaration (certain locals in C# are already implicitly readonly, such as the iteration variable in a ‘foreach’ loop or the used variable in a ‘using’ block, but currently a developer has no ability to mark other locals as ‘readonly’).
This is particularly valuable when working with lambdas and closures. When an anonymous method or lambda accesses local state from the enclosing scope, that state is captured into a closure by the compiler, which is represented by a “display class.” Each “local” that’s captured is a field in this class, yet because the compiler is generating this field on your behalf, you have no opportunity to annotate it as ‘readonly’ in order to prevent the lambda from erroneously writing to the “local” (in quotes because it’s really not a local, at least not in the resulting MSIL). With 'readonly' locals, the compiler can prevent the lambda from writing to local, which is particularly valuable in scenarios involving multithreading where an erroneous write could result in a dangerous but rare and hard-to-find concurrency bug.
Solution: readonly parameters
As a special form of local, parameters should also be annotatable as 'readonly'. This would have no effect on what the caller of the method is able to pass to the parameter (just as there’s no constraint on what values may be stored into a ‘readonly’ field), but as with any ‘readonly’ local, the compiler would prohibit code from writing to the parameter after declaration, which means the body of the method is prohibited from writing to the parameter.
This support for ‘readonly’ parameters would be particularly helpful for a very specific usage of parameters: passing structs by reference. When a reference type is passed to a method, the state that’s actually passed into the method is a copy of the reference to the object (the reference is passed by value), effectively a pointer-sized piece of data. In contrast, when a struct is passed into a method, a copy of the struct is passed in (the struct’s state is passed by value). If the struct is small, such as is an Int32, this is perfectly fine and there’s little better that could be done by the developer. However, when the struct is large (for example, the System.Windows.Media.Media3D.Matrix3D struct contains 16 doubles, making it 128 bytes in size), it can become quite expensive to continually pass around copies of the data. In these cases, developers often resort to passing structs by ref, passing a pointer to the original data rather than making a copy. This avoids the performance overhead, but it now inadvertenly enables the called method to update the original struct’s value:
By enabling marking parameters ‘readonly’, the struct could be passed by reference but still prevent the callee from mutating the original value:
This would also enable readonly struct fields to be passed by ref (to a readonly ref parameter), whereas in C# today passing a readonly struct field by ref is never allowed.
As with ‘readonly’ on fields, ‘readonly’ as it applies to locals and parameters would be shallow rather than deep, meaning that it would prohibit writing to the readonly field, but it wouldn't prevent mutation of any state contained in objects referenced from the ‘readonly’ value.
Additional Considerations
In many situations, in particular when "var" is used to declare a local with type inference, the developer often would like 'readonly' behavior for that local. This is possible if 'readonly' is allowed on locals, e.g.
but it makes the desired immutability harder to express than mutability. To combat that, as shorthand for 'readonly var', we could also introduce a new 'let' or 'val' syntax, with the same meaning as 'readonly var':
The text was updated successfully, but these errors were encountered: