Does the 'readonly' modifier create a hidden copy of a field?Why calling Dispose() on BinaryReader results in compile error?What are the benefits to marking a field as `readonly` in C#?Is it ok to use the same interface definition but provide different behaviour?Why does Microsoft advise against readonly fields with mutable values?Passed value is not returnedHow to avoid “too many parameters” problem in API design?How to create readonly textbox in ASP.NET MVC3 RazorIf a folder does not exist, create itC# IDisposable correct usage and callHow to register and use different implementation of same interface?
The rigidity of the countable product of free groups
Why did Old English lose both thorn and eth?
Yet another hash table in C
The joke office
Efficiently defining a SparseArray function
What's it called when the bad guy gets eaten?
Why different specifications for telescopes and binoculars?
How to know if blackberries are safe to eat
Given a 32 bit number, what is an efficient way to scale each byte by a certain factor?
Through: how to use it with subtraction of functions?
What are the original Russian words for a prostitute?
Why does the Antonov AN-225 not have any winglets?
Why weren't bootable game disks ever a thing on the IBM PC?
What is the minimum time required for final wash in film development?
Would a carnivorous diet be able to support a giant worm?
When did "&" stop being taught alongside the alphabet?
What minifigure is this?
How can a dictatorship government be beneficial to a dictator in a post-scarcity society?
The three greedy pirates
How would the law enforce a ban on immortality?
Misrepresented my work history
What are some further readings in Econometrics you recommend?
Received a dinner invitation through my employer's email, is it ok to attend?
Write a function
Does the 'readonly' modifier create a hidden copy of a field?
Why calling Dispose() on BinaryReader results in compile error?What are the benefits to marking a field as `readonly` in C#?Is it ok to use the same interface definition but provide different behaviour?Why does Microsoft advise against readonly fields with mutable values?Passed value is not returnedHow to avoid “too many parameters” problem in API design?How to create readonly textbox in ASP.NET MVC3 RazorIf a folder does not exist, create itC# IDisposable correct usage and callHow to register and use different implementation of same interface?
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
The only difference between MutableSlab
and ImmutableSlab
implementations is the readonly
modifier applied on the handle
field:
using System;
using System.Runtime.InteropServices;
public class Program
class MutableSlab : IDisposable
private GCHandle handle;
public MutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
class ImmutableSlab : IDisposable
private readonly GCHandle handle;
public ImmutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
public static void Main()
var mutableSlab = new MutableSlab();
var immutableSlab = new ImmutableSlab();
mutableSlab.Dispose();
immutableSlab.Dispose();
Console.WriteLine($"nameof(mutableSlab).handle.IsAllocated = mutableSlab.IsAllocated");
Console.WriteLine($"nameof(immutableSlab).handle.IsAllocated = immutableSlab.IsAllocated");
But they produce different results:
mutableSlab.handle.IsAllocated = False
immutableSlab.handle.IsAllocated = True
GCHandle is a mutable struct and when you copy it then it behaves exactly like in scenario with immutableSlab
.
Does the readonly
modifier create a hidden copy of a field? Does it mean that it's not only a compile-time check? I couldn't find anything about this behaviour here. Is this behaviour documented?
c# struct value-type readonly-attribute
add a comment |
The only difference between MutableSlab
and ImmutableSlab
implementations is the readonly
modifier applied on the handle
field:
using System;
using System.Runtime.InteropServices;
public class Program
class MutableSlab : IDisposable
private GCHandle handle;
public MutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
class ImmutableSlab : IDisposable
private readonly GCHandle handle;
public ImmutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
public static void Main()
var mutableSlab = new MutableSlab();
var immutableSlab = new ImmutableSlab();
mutableSlab.Dispose();
immutableSlab.Dispose();
Console.WriteLine($"nameof(mutableSlab).handle.IsAllocated = mutableSlab.IsAllocated");
Console.WriteLine($"nameof(immutableSlab).handle.IsAllocated = immutableSlab.IsAllocated");
But they produce different results:
mutableSlab.handle.IsAllocated = False
immutableSlab.handle.IsAllocated = True
GCHandle is a mutable struct and when you copy it then it behaves exactly like in scenario with immutableSlab
.
Does the readonly
modifier create a hidden copy of a field? Does it mean that it's not only a compile-time check? I couldn't find anything about this behaviour here. Is this behaviour documented?
c# struct value-type readonly-attribute
1
I won't post this as an answer since I'm not 100% sure about the behaviour of GC. But no, the readonly keyword doesn't introduce new fields. It does what it says on the tin. The behavior you observe is probably due to the GC not doing what you want it to. Try running GC.Collect(). The GC takes hints, not orders usually.
– Markonius
Jul 1 at 7:32
2
I'm writing an answer now... But for those who are impatient, here's a blog post I've written earlier: codeblog.jonskeet.uk/2014/07/16/…
– Jon Skeet
Jul 1 at 7:38
1
Member invocations via the read-only field creates a copy. It's not that there's an extra field - it's that the field is copied before invocation.
– Jon Skeet
Jul 1 at 7:40
1
Note that Resharper actually warns about this; forthis.handle.Free();
inImmutableSlab
it gives the warning: "Impure method is called for readonly field of value type."
– Matthew Watson
Jul 1 at 7:42
add a comment |
The only difference between MutableSlab
and ImmutableSlab
implementations is the readonly
modifier applied on the handle
field:
using System;
using System.Runtime.InteropServices;
public class Program
class MutableSlab : IDisposable
private GCHandle handle;
public MutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
class ImmutableSlab : IDisposable
private readonly GCHandle handle;
public ImmutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
public static void Main()
var mutableSlab = new MutableSlab();
var immutableSlab = new ImmutableSlab();
mutableSlab.Dispose();
immutableSlab.Dispose();
Console.WriteLine($"nameof(mutableSlab).handle.IsAllocated = mutableSlab.IsAllocated");
Console.WriteLine($"nameof(immutableSlab).handle.IsAllocated = immutableSlab.IsAllocated");
But they produce different results:
mutableSlab.handle.IsAllocated = False
immutableSlab.handle.IsAllocated = True
GCHandle is a mutable struct and when you copy it then it behaves exactly like in scenario with immutableSlab
.
Does the readonly
modifier create a hidden copy of a field? Does it mean that it's not only a compile-time check? I couldn't find anything about this behaviour here. Is this behaviour documented?
c# struct value-type readonly-attribute
The only difference between MutableSlab
and ImmutableSlab
implementations is the readonly
modifier applied on the handle
field:
using System;
using System.Runtime.InteropServices;
public class Program
class MutableSlab : IDisposable
private GCHandle handle;
public MutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
class ImmutableSlab : IDisposable
private readonly GCHandle handle;
public ImmutableSlab()
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
this.handle.Free();
public static void Main()
var mutableSlab = new MutableSlab();
var immutableSlab = new ImmutableSlab();
mutableSlab.Dispose();
immutableSlab.Dispose();
Console.WriteLine($"nameof(mutableSlab).handle.IsAllocated = mutableSlab.IsAllocated");
Console.WriteLine($"nameof(immutableSlab).handle.IsAllocated = immutableSlab.IsAllocated");
But they produce different results:
mutableSlab.handle.IsAllocated = False
immutableSlab.handle.IsAllocated = True
GCHandle is a mutable struct and when you copy it then it behaves exactly like in scenario with immutableSlab
.
Does the readonly
modifier create a hidden copy of a field? Does it mean that it's not only a compile-time check? I couldn't find anything about this behaviour here. Is this behaviour documented?
c# struct value-type readonly-attribute
c# struct value-type readonly-attribute
edited Jul 1 at 23:01
Peter Mortensen
14.2k19 gold badges88 silver badges115 bronze badges
14.2k19 gold badges88 silver badges115 bronze badges
asked Jul 1 at 7:26
BARTBART
5333 silver badges10 bronze badges
5333 silver badges10 bronze badges
1
I won't post this as an answer since I'm not 100% sure about the behaviour of GC. But no, the readonly keyword doesn't introduce new fields. It does what it says on the tin. The behavior you observe is probably due to the GC not doing what you want it to. Try running GC.Collect(). The GC takes hints, not orders usually.
– Markonius
Jul 1 at 7:32
2
I'm writing an answer now... But for those who are impatient, here's a blog post I've written earlier: codeblog.jonskeet.uk/2014/07/16/…
– Jon Skeet
Jul 1 at 7:38
1
Member invocations via the read-only field creates a copy. It's not that there's an extra field - it's that the field is copied before invocation.
– Jon Skeet
Jul 1 at 7:40
1
Note that Resharper actually warns about this; forthis.handle.Free();
inImmutableSlab
it gives the warning: "Impure method is called for readonly field of value type."
– Matthew Watson
Jul 1 at 7:42
add a comment |
1
I won't post this as an answer since I'm not 100% sure about the behaviour of GC. But no, the readonly keyword doesn't introduce new fields. It does what it says on the tin. The behavior you observe is probably due to the GC not doing what you want it to. Try running GC.Collect(). The GC takes hints, not orders usually.
– Markonius
Jul 1 at 7:32
2
I'm writing an answer now... But for those who are impatient, here's a blog post I've written earlier: codeblog.jonskeet.uk/2014/07/16/…
– Jon Skeet
Jul 1 at 7:38
1
Member invocations via the read-only field creates a copy. It's not that there's an extra field - it's that the field is copied before invocation.
– Jon Skeet
Jul 1 at 7:40
1
Note that Resharper actually warns about this; forthis.handle.Free();
inImmutableSlab
it gives the warning: "Impure method is called for readonly field of value type."
– Matthew Watson
Jul 1 at 7:42
1
1
I won't post this as an answer since I'm not 100% sure about the behaviour of GC. But no, the readonly keyword doesn't introduce new fields. It does what it says on the tin. The behavior you observe is probably due to the GC not doing what you want it to. Try running GC.Collect(). The GC takes hints, not orders usually.
– Markonius
Jul 1 at 7:32
I won't post this as an answer since I'm not 100% sure about the behaviour of GC. But no, the readonly keyword doesn't introduce new fields. It does what it says on the tin. The behavior you observe is probably due to the GC not doing what you want it to. Try running GC.Collect(). The GC takes hints, not orders usually.
– Markonius
Jul 1 at 7:32
2
2
I'm writing an answer now... But for those who are impatient, here's a blog post I've written earlier: codeblog.jonskeet.uk/2014/07/16/…
– Jon Skeet
Jul 1 at 7:38
I'm writing an answer now... But for those who are impatient, here's a blog post I've written earlier: codeblog.jonskeet.uk/2014/07/16/…
– Jon Skeet
Jul 1 at 7:38
1
1
Member invocations via the read-only field creates a copy. It's not that there's an extra field - it's that the field is copied before invocation.
– Jon Skeet
Jul 1 at 7:40
Member invocations via the read-only field creates a copy. It's not that there's an extra field - it's that the field is copied before invocation.
– Jon Skeet
Jul 1 at 7:40
1
1
Note that Resharper actually warns about this; for
this.handle.Free();
in ImmutableSlab
it gives the warning: "Impure method is called for readonly field of value type."– Matthew Watson
Jul 1 at 7:42
Note that Resharper actually warns about this; for
this.handle.Free();
in ImmutableSlab
it gives the warning: "Impure method is called for readonly field of value type."– Matthew Watson
Jul 1 at 7:42
add a comment |
1 Answer
1
active
oldest
votes
Does the
readonly
modifier create a hidden copy of a field?
Calling a method or property on a read-only field of a regular struct type (outside the constructor or static constructor) first copies the field, yes. That's because the compiler doesn't know whether the property or method access would modify the value you call it on.
From the C# 5 ECMA specification:
Section 12.7.5.1 (Member access, general)
This classifies member accesses, including:
- If I identifies a static field:
- If the field is readonly and the reference occurs outside the static constructor of the class or struct in which the field is declared, then the result is a value, namely the value of the static field I in E.
- Otherwise, the result is a variable, namely the static field I in E.
And:
- If T is a struct-type and I identifies an instance field of that struct-type:
- If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value, namely the value of the field I in the struct instance given by E.
- Otherwise, the result is a variable, namely the field I in the struct instance given by E.
I'm not sure why the instance field part specifically refers to struct types, but the static field part doesn't. The important part is whether the expression is classified as a variable or a value. That's then important in function member invocation...
Section 12.6.6.1 (Function member invocation, general)
The run-time processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:
[...]
- Otherwise, if the type of E is a value-type V, and M is declared or overridden in V:
- [...]
- If E is not classified as a variable, then a temporary local variable of E's type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.
Here's a self-contained example:
using System;
using System.Globalization;
struct Counter
private int count;
public int IncrementedCount => ++count;
class Test
static readonly Counter readOnlyCounter;
static Counter readWriteCounter;
static void Main()
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 2
Console.WriteLine(readWriteCounter.IncrementedCount); // 3
Here's the IL for a call to readOnlyCounter.IncrementedCount
:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
That copies the field value onto the stack, then calls the property... so the value of the field doesn't end up changing; it's incrementing count
within the copy.
Compare that with the IL for the read-write field:
ldsflda valuetype Counter Test::readWriteCounter
call instance int32 Counter::get_IncrementedCount()
That makes the call directly on the field, so the field value ends up changing within the property.
Making a copy can be inefficient when the struct is large and the member doesn't mutate it. That's why in C# 7.2 and above, the readonly
modifier can be applied to a struct. Here's another example:
using System;
using System.Globalization;
readonly struct ReadOnlyStruct
public void NoOp()
class Test
static readonly ReadOnlyStruct field1;
static ReadOnlyStruct field2;
static void Main()
field1.NoOp();
field2.NoOp();
With the readonly
modifier on the struct itself, the field1.NoOp()
call doesn't create a copy. If you remove the readonly
modifier and recompile, you'll see that it creates a copy just like it did in readOnlyCounter.IncrementedCount
.
I have a blog post from 2014 that I wrote having found that readonly
fields were causing performance issues in Noda Time. Fortunately that's now fixed using the readonly
modifier on the structs instead.
Calling a method or property on a read-only field of a regular struct type first copies the field
. I couldn't find this statement in documentation, but I think this is the implicit version of it:Because value types directly contain their data, a field that is a readonly value type is immutable
. So if the field is not a readonly struct then when I call impure method that changes the state of this field then it must create a copy. Am I right?
– BART
Jul 1 at 8:12
@BART: Yes. Which documentation were you looking at? It is in the C# specification somewhere, but it may not be terribly easy to find.
– Jon Skeet
Jul 1 at 8:27
@Jon Skeet I was looking at the microsoft site readonly keyword, but there is no such explicit statement like yours. I will take a deeper look at C# specification. Thanks for the explanation
– BART
Jul 1 at 8:39
@BART: I've now quoted the relevant bit of the spec, and I'll look at the confusing part of the text.
– Jon Skeet
Jul 1 at 8:39
Thanks for the comprehensive explanation, Jon! Before that I thought that readonly was only referring to the reference variable, not to the object itself. I learned something new! Thumbs up for that answer :-)
– Matt
Jul 1 at 9:21
|
show 7 more comments
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56831532%2fdoes-the-readonly-modifier-create-a-hidden-copy-of-a-field%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Does the
readonly
modifier create a hidden copy of a field?
Calling a method or property on a read-only field of a regular struct type (outside the constructor or static constructor) first copies the field, yes. That's because the compiler doesn't know whether the property or method access would modify the value you call it on.
From the C# 5 ECMA specification:
Section 12.7.5.1 (Member access, general)
This classifies member accesses, including:
- If I identifies a static field:
- If the field is readonly and the reference occurs outside the static constructor of the class or struct in which the field is declared, then the result is a value, namely the value of the static field I in E.
- Otherwise, the result is a variable, namely the static field I in E.
And:
- If T is a struct-type and I identifies an instance field of that struct-type:
- If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value, namely the value of the field I in the struct instance given by E.
- Otherwise, the result is a variable, namely the field I in the struct instance given by E.
I'm not sure why the instance field part specifically refers to struct types, but the static field part doesn't. The important part is whether the expression is classified as a variable or a value. That's then important in function member invocation...
Section 12.6.6.1 (Function member invocation, general)
The run-time processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:
[...]
- Otherwise, if the type of E is a value-type V, and M is declared or overridden in V:
- [...]
- If E is not classified as a variable, then a temporary local variable of E's type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.
Here's a self-contained example:
using System;
using System.Globalization;
struct Counter
private int count;
public int IncrementedCount => ++count;
class Test
static readonly Counter readOnlyCounter;
static Counter readWriteCounter;
static void Main()
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 2
Console.WriteLine(readWriteCounter.IncrementedCount); // 3
Here's the IL for a call to readOnlyCounter.IncrementedCount
:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
That copies the field value onto the stack, then calls the property... so the value of the field doesn't end up changing; it's incrementing count
within the copy.
Compare that with the IL for the read-write field:
ldsflda valuetype Counter Test::readWriteCounter
call instance int32 Counter::get_IncrementedCount()
That makes the call directly on the field, so the field value ends up changing within the property.
Making a copy can be inefficient when the struct is large and the member doesn't mutate it. That's why in C# 7.2 and above, the readonly
modifier can be applied to a struct. Here's another example:
using System;
using System.Globalization;
readonly struct ReadOnlyStruct
public void NoOp()
class Test
static readonly ReadOnlyStruct field1;
static ReadOnlyStruct field2;
static void Main()
field1.NoOp();
field2.NoOp();
With the readonly
modifier on the struct itself, the field1.NoOp()
call doesn't create a copy. If you remove the readonly
modifier and recompile, you'll see that it creates a copy just like it did in readOnlyCounter.IncrementedCount
.
I have a blog post from 2014 that I wrote having found that readonly
fields were causing performance issues in Noda Time. Fortunately that's now fixed using the readonly
modifier on the structs instead.
Calling a method or property on a read-only field of a regular struct type first copies the field
. I couldn't find this statement in documentation, but I think this is the implicit version of it:Because value types directly contain their data, a field that is a readonly value type is immutable
. So if the field is not a readonly struct then when I call impure method that changes the state of this field then it must create a copy. Am I right?
– BART
Jul 1 at 8:12
@BART: Yes. Which documentation were you looking at? It is in the C# specification somewhere, but it may not be terribly easy to find.
– Jon Skeet
Jul 1 at 8:27
@Jon Skeet I was looking at the microsoft site readonly keyword, but there is no such explicit statement like yours. I will take a deeper look at C# specification. Thanks for the explanation
– BART
Jul 1 at 8:39
@BART: I've now quoted the relevant bit of the spec, and I'll look at the confusing part of the text.
– Jon Skeet
Jul 1 at 8:39
Thanks for the comprehensive explanation, Jon! Before that I thought that readonly was only referring to the reference variable, not to the object itself. I learned something new! Thumbs up for that answer :-)
– Matt
Jul 1 at 9:21
|
show 7 more comments
Does the
readonly
modifier create a hidden copy of a field?
Calling a method or property on a read-only field of a regular struct type (outside the constructor or static constructor) first copies the field, yes. That's because the compiler doesn't know whether the property or method access would modify the value you call it on.
From the C# 5 ECMA specification:
Section 12.7.5.1 (Member access, general)
This classifies member accesses, including:
- If I identifies a static field:
- If the field is readonly and the reference occurs outside the static constructor of the class or struct in which the field is declared, then the result is a value, namely the value of the static field I in E.
- Otherwise, the result is a variable, namely the static field I in E.
And:
- If T is a struct-type and I identifies an instance field of that struct-type:
- If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value, namely the value of the field I in the struct instance given by E.
- Otherwise, the result is a variable, namely the field I in the struct instance given by E.
I'm not sure why the instance field part specifically refers to struct types, but the static field part doesn't. The important part is whether the expression is classified as a variable or a value. That's then important in function member invocation...
Section 12.6.6.1 (Function member invocation, general)
The run-time processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:
[...]
- Otherwise, if the type of E is a value-type V, and M is declared or overridden in V:
- [...]
- If E is not classified as a variable, then a temporary local variable of E's type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.
Here's a self-contained example:
using System;
using System.Globalization;
struct Counter
private int count;
public int IncrementedCount => ++count;
class Test
static readonly Counter readOnlyCounter;
static Counter readWriteCounter;
static void Main()
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 2
Console.WriteLine(readWriteCounter.IncrementedCount); // 3
Here's the IL for a call to readOnlyCounter.IncrementedCount
:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
That copies the field value onto the stack, then calls the property... so the value of the field doesn't end up changing; it's incrementing count
within the copy.
Compare that with the IL for the read-write field:
ldsflda valuetype Counter Test::readWriteCounter
call instance int32 Counter::get_IncrementedCount()
That makes the call directly on the field, so the field value ends up changing within the property.
Making a copy can be inefficient when the struct is large and the member doesn't mutate it. That's why in C# 7.2 and above, the readonly
modifier can be applied to a struct. Here's another example:
using System;
using System.Globalization;
readonly struct ReadOnlyStruct
public void NoOp()
class Test
static readonly ReadOnlyStruct field1;
static ReadOnlyStruct field2;
static void Main()
field1.NoOp();
field2.NoOp();
With the readonly
modifier on the struct itself, the field1.NoOp()
call doesn't create a copy. If you remove the readonly
modifier and recompile, you'll see that it creates a copy just like it did in readOnlyCounter.IncrementedCount
.
I have a blog post from 2014 that I wrote having found that readonly
fields were causing performance issues in Noda Time. Fortunately that's now fixed using the readonly
modifier on the structs instead.
Calling a method or property on a read-only field of a regular struct type first copies the field
. I couldn't find this statement in documentation, but I think this is the implicit version of it:Because value types directly contain their data, a field that is a readonly value type is immutable
. So if the field is not a readonly struct then when I call impure method that changes the state of this field then it must create a copy. Am I right?
– BART
Jul 1 at 8:12
@BART: Yes. Which documentation were you looking at? It is in the C# specification somewhere, but it may not be terribly easy to find.
– Jon Skeet
Jul 1 at 8:27
@Jon Skeet I was looking at the microsoft site readonly keyword, but there is no such explicit statement like yours. I will take a deeper look at C# specification. Thanks for the explanation
– BART
Jul 1 at 8:39
@BART: I've now quoted the relevant bit of the spec, and I'll look at the confusing part of the text.
– Jon Skeet
Jul 1 at 8:39
Thanks for the comprehensive explanation, Jon! Before that I thought that readonly was only referring to the reference variable, not to the object itself. I learned something new! Thumbs up for that answer :-)
– Matt
Jul 1 at 9:21
|
show 7 more comments
Does the
readonly
modifier create a hidden copy of a field?
Calling a method or property on a read-only field of a regular struct type (outside the constructor or static constructor) first copies the field, yes. That's because the compiler doesn't know whether the property or method access would modify the value you call it on.
From the C# 5 ECMA specification:
Section 12.7.5.1 (Member access, general)
This classifies member accesses, including:
- If I identifies a static field:
- If the field is readonly and the reference occurs outside the static constructor of the class or struct in which the field is declared, then the result is a value, namely the value of the static field I in E.
- Otherwise, the result is a variable, namely the static field I in E.
And:
- If T is a struct-type and I identifies an instance field of that struct-type:
- If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value, namely the value of the field I in the struct instance given by E.
- Otherwise, the result is a variable, namely the field I in the struct instance given by E.
I'm not sure why the instance field part specifically refers to struct types, but the static field part doesn't. The important part is whether the expression is classified as a variable or a value. That's then important in function member invocation...
Section 12.6.6.1 (Function member invocation, general)
The run-time processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:
[...]
- Otherwise, if the type of E is a value-type V, and M is declared or overridden in V:
- [...]
- If E is not classified as a variable, then a temporary local variable of E's type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.
Here's a self-contained example:
using System;
using System.Globalization;
struct Counter
private int count;
public int IncrementedCount => ++count;
class Test
static readonly Counter readOnlyCounter;
static Counter readWriteCounter;
static void Main()
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 2
Console.WriteLine(readWriteCounter.IncrementedCount); // 3
Here's the IL for a call to readOnlyCounter.IncrementedCount
:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
That copies the field value onto the stack, then calls the property... so the value of the field doesn't end up changing; it's incrementing count
within the copy.
Compare that with the IL for the read-write field:
ldsflda valuetype Counter Test::readWriteCounter
call instance int32 Counter::get_IncrementedCount()
That makes the call directly on the field, so the field value ends up changing within the property.
Making a copy can be inefficient when the struct is large and the member doesn't mutate it. That's why in C# 7.2 and above, the readonly
modifier can be applied to a struct. Here's another example:
using System;
using System.Globalization;
readonly struct ReadOnlyStruct
public void NoOp()
class Test
static readonly ReadOnlyStruct field1;
static ReadOnlyStruct field2;
static void Main()
field1.NoOp();
field2.NoOp();
With the readonly
modifier on the struct itself, the field1.NoOp()
call doesn't create a copy. If you remove the readonly
modifier and recompile, you'll see that it creates a copy just like it did in readOnlyCounter.IncrementedCount
.
I have a blog post from 2014 that I wrote having found that readonly
fields were causing performance issues in Noda Time. Fortunately that's now fixed using the readonly
modifier on the structs instead.
Does the
readonly
modifier create a hidden copy of a field?
Calling a method or property on a read-only field of a regular struct type (outside the constructor or static constructor) first copies the field, yes. That's because the compiler doesn't know whether the property or method access would modify the value you call it on.
From the C# 5 ECMA specification:
Section 12.7.5.1 (Member access, general)
This classifies member accesses, including:
- If I identifies a static field:
- If the field is readonly and the reference occurs outside the static constructor of the class or struct in which the field is declared, then the result is a value, namely the value of the static field I in E.
- Otherwise, the result is a variable, namely the static field I in E.
And:
- If T is a struct-type and I identifies an instance field of that struct-type:
- If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value, namely the value of the field I in the struct instance given by E.
- Otherwise, the result is a variable, namely the field I in the struct instance given by E.
I'm not sure why the instance field part specifically refers to struct types, but the static field part doesn't. The important part is whether the expression is classified as a variable or a value. That's then important in function member invocation...
Section 12.6.6.1 (Function member invocation, general)
The run-time processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:
[...]
- Otherwise, if the type of E is a value-type V, and M is declared or overridden in V:
- [...]
- If E is not classified as a variable, then a temporary local variable of E's type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.
Here's a self-contained example:
using System;
using System.Globalization;
struct Counter
private int count;
public int IncrementedCount => ++count;
class Test
static readonly Counter readOnlyCounter;
static Counter readWriteCounter;
static void Main()
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 2
Console.WriteLine(readWriteCounter.IncrementedCount); // 3
Here's the IL for a call to readOnlyCounter.IncrementedCount
:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
That copies the field value onto the stack, then calls the property... so the value of the field doesn't end up changing; it's incrementing count
within the copy.
Compare that with the IL for the read-write field:
ldsflda valuetype Counter Test::readWriteCounter
call instance int32 Counter::get_IncrementedCount()
That makes the call directly on the field, so the field value ends up changing within the property.
Making a copy can be inefficient when the struct is large and the member doesn't mutate it. That's why in C# 7.2 and above, the readonly
modifier can be applied to a struct. Here's another example:
using System;
using System.Globalization;
readonly struct ReadOnlyStruct
public void NoOp()
class Test
static readonly ReadOnlyStruct field1;
static ReadOnlyStruct field2;
static void Main()
field1.NoOp();
field2.NoOp();
With the readonly
modifier on the struct itself, the field1.NoOp()
call doesn't create a copy. If you remove the readonly
modifier and recompile, you'll see that it creates a copy just like it did in readOnlyCounter.IncrementedCount
.
I have a blog post from 2014 that I wrote having found that readonly
fields were causing performance issues in Noda Time. Fortunately that's now fixed using the readonly
modifier on the structs instead.
edited Jul 1 at 23:01
Peter Mortensen
14.2k19 gold badges88 silver badges115 bronze badges
14.2k19 gold badges88 silver badges115 bronze badges
answered Jul 1 at 7:46
Jon SkeetJon Skeet
1117k709 gold badges8134 silver badges8540 bronze badges
1117k709 gold badges8134 silver badges8540 bronze badges
Calling a method or property on a read-only field of a regular struct type first copies the field
. I couldn't find this statement in documentation, but I think this is the implicit version of it:Because value types directly contain their data, a field that is a readonly value type is immutable
. So if the field is not a readonly struct then when I call impure method that changes the state of this field then it must create a copy. Am I right?
– BART
Jul 1 at 8:12
@BART: Yes. Which documentation were you looking at? It is in the C# specification somewhere, but it may not be terribly easy to find.
– Jon Skeet
Jul 1 at 8:27
@Jon Skeet I was looking at the microsoft site readonly keyword, but there is no such explicit statement like yours. I will take a deeper look at C# specification. Thanks for the explanation
– BART
Jul 1 at 8:39
@BART: I've now quoted the relevant bit of the spec, and I'll look at the confusing part of the text.
– Jon Skeet
Jul 1 at 8:39
Thanks for the comprehensive explanation, Jon! Before that I thought that readonly was only referring to the reference variable, not to the object itself. I learned something new! Thumbs up for that answer :-)
– Matt
Jul 1 at 9:21
|
show 7 more comments
Calling a method or property on a read-only field of a regular struct type first copies the field
. I couldn't find this statement in documentation, but I think this is the implicit version of it:Because value types directly contain their data, a field that is a readonly value type is immutable
. So if the field is not a readonly struct then when I call impure method that changes the state of this field then it must create a copy. Am I right?
– BART
Jul 1 at 8:12
@BART: Yes. Which documentation were you looking at? It is in the C# specification somewhere, but it may not be terribly easy to find.
– Jon Skeet
Jul 1 at 8:27
@Jon Skeet I was looking at the microsoft site readonly keyword, but there is no such explicit statement like yours. I will take a deeper look at C# specification. Thanks for the explanation
– BART
Jul 1 at 8:39
@BART: I've now quoted the relevant bit of the spec, and I'll look at the confusing part of the text.
– Jon Skeet
Jul 1 at 8:39
Thanks for the comprehensive explanation, Jon! Before that I thought that readonly was only referring to the reference variable, not to the object itself. I learned something new! Thumbs up for that answer :-)
– Matt
Jul 1 at 9:21
Calling a method or property on a read-only field of a regular struct type first copies the field
. I couldn't find this statement in documentation, but I think this is the implicit version of it: Because value types directly contain their data, a field that is a readonly value type is immutable
. So if the field is not a readonly struct then when I call impure method that changes the state of this field then it must create a copy. Am I right?– BART
Jul 1 at 8:12
Calling a method or property on a read-only field of a regular struct type first copies the field
. I couldn't find this statement in documentation, but I think this is the implicit version of it: Because value types directly contain their data, a field that is a readonly value type is immutable
. So if the field is not a readonly struct then when I call impure method that changes the state of this field then it must create a copy. Am I right?– BART
Jul 1 at 8:12
@BART: Yes. Which documentation were you looking at? It is in the C# specification somewhere, but it may not be terribly easy to find.
– Jon Skeet
Jul 1 at 8:27
@BART: Yes. Which documentation were you looking at? It is in the C# specification somewhere, but it may not be terribly easy to find.
– Jon Skeet
Jul 1 at 8:27
@Jon Skeet I was looking at the microsoft site readonly keyword, but there is no such explicit statement like yours. I will take a deeper look at C# specification. Thanks for the explanation
– BART
Jul 1 at 8:39
@Jon Skeet I was looking at the microsoft site readonly keyword, but there is no such explicit statement like yours. I will take a deeper look at C# specification. Thanks for the explanation
– BART
Jul 1 at 8:39
@BART: I've now quoted the relevant bit of the spec, and I'll look at the confusing part of the text.
– Jon Skeet
Jul 1 at 8:39
@BART: I've now quoted the relevant bit of the spec, and I'll look at the confusing part of the text.
– Jon Skeet
Jul 1 at 8:39
Thanks for the comprehensive explanation, Jon! Before that I thought that readonly was only referring to the reference variable, not to the object itself. I learned something new! Thumbs up for that answer :-)
– Matt
Jul 1 at 9:21
Thanks for the comprehensive explanation, Jon! Before that I thought that readonly was only referring to the reference variable, not to the object itself. I learned something new! Thumbs up for that answer :-)
– Matt
Jul 1 at 9:21
|
show 7 more comments
Got a question that you can’t ask on public Stack Overflow? Learn more about sharing private information with Stack Overflow for Teams.
Got a question that you can’t ask on public Stack Overflow? Learn more about sharing private information with Stack Overflow for Teams.
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56831532%2fdoes-the-readonly-modifier-create-a-hidden-copy-of-a-field%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
I won't post this as an answer since I'm not 100% sure about the behaviour of GC. But no, the readonly keyword doesn't introduce new fields. It does what it says on the tin. The behavior you observe is probably due to the GC not doing what you want it to. Try running GC.Collect(). The GC takes hints, not orders usually.
– Markonius
Jul 1 at 7:32
2
I'm writing an answer now... But for those who are impatient, here's a blog post I've written earlier: codeblog.jonskeet.uk/2014/07/16/…
– Jon Skeet
Jul 1 at 7:38
1
Member invocations via the read-only field creates a copy. It's not that there's an extra field - it's that the field is copied before invocation.
– Jon Skeet
Jul 1 at 7:40
1
Note that Resharper actually warns about this; for
this.handle.Free();
inImmutableSlab
it gives the warning: "Impure method is called for readonly field of value type."– Matthew Watson
Jul 1 at 7:42