1

I have a read-only object but somewhere it's properties getting updated. Does C# have anything to restrict that too from direct changes as well as via reflection?

Here is the POC code

class Program
{
    static void Main(string[] args)
    {
        ReadOnlyCreator tester = new ReadOnlyCreator();
        tester.ModifyTester();
        Console.ReadLine();
    }
}

class ClassUnderTest
{
    public string SomeProp { get; set; }
}

class ReadOnlyCreator
{
    private readonly ClassUnderTest _classUnderTest;
    public ReadOnlyCreator()
    {
        _classUnderTest = new ClassUnderTest { SomeProp = "Init" };
    }

    public void ModifyTester()
    {
        Console.WriteLine("Before: " + _classUnderTest.SomeProp);
        var modifier = new Modifier(_classUnderTest);
        modifier.Modify();
        Console.WriteLine("After: " + _classUnderTest.SomeProp);
    }
}
class Modifier
{
    private ClassUnderTest _classUnderTest;
    public Modifier(ClassUnderTest classUnderTest)
    {
        _classUnderTest = classUnderTest;
    }

    public void Modify()
    {
        _classUnderTest.SomeProp = "Modified";
    }
Imad
  • 7,126
  • 12
  • 55
  • 112
  • Readonly in this context means you can't reassign `_classUnderTest`, so `_classUnderTest = new ClassUnderTest()` outside of the constructor fails but as you noticed you can still edit the properties of `_classUnderTest`. You'll need to make `set` private or remove it and then use a constructor parameter to set it – MindSwipe Apr 30 '21 at 09:22
  • Does this answer your question? [How can I protect my private funcs against reflection executing?](https://stackoverflow.com/questions/8357469/how-can-i-protect-my-private-funcs-against-reflection-executing) – Sinatr Apr 30 '21 at 09:22
  • 2
    _"Does C# have anything to restrict that"_ - yes! It's called [`record`s](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#record-types) and are new in C# 9. `record`s are **immutable** _reference_ types. The compiler also synthesizes Public init-only properties for each parameter of a primary constructor. https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#define-compiler-synthesized-methods –  Apr 30 '21 at 09:35
  • @MickyD records aren't truly immutable; they aren't any more immutable than a type with a `get`-only property, or an `init` property; they change a lot of other things, though; I would absolutely not recommend casually switching to records just to make something quasi-immutable – Marc Gravell Apr 30 '21 at 09:38
  • @MarcGravell well...darn. Is that because you can still call the setters from reflection? Have you got a link? –  Apr 30 '21 at 09:39
  • 2
    @MickyD You can define mutable properties in records. It's right there in your link: _"You can also create record types with mutable properties and fields: ..."_ - just the "default" is "immutable". And of course: reflection destroys everything. – Fildor Apr 30 '21 at 09:45
  • 1
    @Fildor thanks. Looks like `record` brings along the baggage :( –  Apr 30 '21 at 09:55

1 Answers1

6

If you want a read only object you should make it read only. i.e.

class ClassUnderTest
{
    public string SomeProp { get;  }
    public ClassUnderTest(string someProp) => SomeProp = someProp;
}

You could also use init only setters if you are using c#9. This allow the property to be set only during construction of the object:

class ClassUnderTest
{
    public string SomeProp { get; init; }
}

If you are using value types you can (and should) declare the whole type as readonly. See also Choosing Between Class and Struct

public readonly struct StructUnderTest

This does not protect against reflection or unsafe code. Using these features is deliberately circumventing the rules, so it is up to the developer to ensure it is safe.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • 2
    @Liam `struct` by itself doesn't make it immutable; it is the `readonly` in the `readonly struct` that does this, and: that's *what OP wants*; there are *other* implications of the `struct` part though - around equality, boxing, copy-by-value, and a few other things that are far more relevant – Marc Gravell Apr 30 '21 at 09:35
  • 1
    IMO you should add an `init` example, in addition to just linking it – Marc Gravell Apr 30 '21 at 09:37
  • Since this is new learning for me going forward I am accepting it as answer but in my project C# 8 is used. Is there any thing for c# 8? – Imad Apr 30 '21 at 09:47
  • 2
    @Imad The first example works just fine in c# 8 and earlier. You will need to create a constructor, but that is usually a good idea anyway to ensure all properties are actually set. – JonasH Apr 30 '21 at 09:52