Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use case to understand why a list of strings should be declared as readonly

Tags:

c#

clr

I am trying to understand what use cases would require me to declare a List<string> as a ReadOnly type.

An associated question with this is: How much memory upon instantiation of a list gets allocated?

like image 475
Vivek Shukla Avatar asked Jan 29 '18 11:01

Vivek Shukla


People also ask

What is the purpose of read only in C#?

In a field declaration, readonly indicates that assignment to the field can only occur as part of the declaration or in a constructor in the same class. A readonly field can be assigned and reassigned multiple times within the field declaration and constructor.

How do you make a variable read only in C#?

In C#, you can use a readonly keyword to declare a readonly variable. This readonly keyword shows that you can assign the variable only when you declare a variable or in a constructor of the same class in which it is declared.

What is private readonly?

If it's private and readonly , the benefit is that you can't inadvertently change it from another part of that class after it is initialized. The readonly modifier ensures the field can only be given a value during its initialization or in its class constructor.


1 Answers

The main reason to mark a field as readonly is so that you know that regular code cannot have swapped the list reference. One key scenario where that might matter is if you have other code in the type that is performing synchronization against the list using a lock(theListField). Obviously if someone swaps the list instance: things will break. Note that in most types that have a list/collection, it isn't expected to change the instance, so this readonly asserts that expectation. A common pattern is:

private List<Foo> _items = new List<Foo>();
public List<Foo> Items => _items;

or:

public List<Foo> Items {get;} = new List<Foo>();

In the first example, it should be perfectly fine to mark that field as readonly:

private readonly List<Foo> _items = new List<Foo>();

Marking a field as readonly has no impact on allocations etc. It also doesn't make the list read-only: just the field. You can still Add() / Remove() / Clear() etc. The only thing you can't do is change the list instance to be a completely different list instance; you can, of course, still completely change the contents. And read-only is a lie anyway: reflection and unsafe code can modify the value of a readonly field.

There is one scenario where readonly can have a negative impact, and that relates to large struct fields and calling methods on them. If the field is readonly, the compiler copies the struct onto the stack before calling the method - rather than executing the method in-place in the field; ldfld + stloc + ldloca (if the field is readonly) vs ldflda (if it isn't marked readonly); this is because the compiler can't trust the method not to mutate the value. It can't even check whether all the fields on the struct are readonly, because that isn't enough: a struct method can rewrite this:

struct EvilStruct
{
    readonly int _id;
    public EvilStruct(int id) { _id = id; }
    public void EvilMethod() { this = new EvilStruct(_id + 1); }
}

Because the compiler is trying to enforce the readonly nature of a field, if you have:

readonly EvilStruct _foo;
//...
_foo.EvilMethod();

it wants to ensure that the EvilMethod() can't overwrite _foo with a new value. Hence the gymnastics and the copy on the stack. Usually this has negligible impact, but if the struct is atypically large, then this can cause a performance problem. The same issue of guaranteeing that the value doesn't change also applies to the new in argument modifier in C# 7.2:

void(in EvilStruct value) {...}

where the caller wants to guarantee that it doesn't change the value (this is actually a ref EvilStruct, so changes would be propagated).

This issue is resolved in C# 7.2 by the addition of the readonly struct syntax - this tells the compiler that it is safe to invoke the method in-situ without having to make the extra stack copy:

readonly struct EvilStruct
{
    readonly int _id;
    public EvilStruct(int id) { _id = id; }
    // the following method no longer compiles:
    // CS1604   Cannot assign to 'this' because it is read-only
    public void EvilMethod() { this = new EvilStruct(_id + 1); }
}

This entire scenario doesn't apply to List<T>, because that is a reference type, not a value type.

like image 197
Marc Gravell Avatar answered Oct 28 '22 03:10

Marc Gravell