23

Coming from a C++ background, I'm used to sticking the const keyword into function definitions to make objects being passed in read-only values. However, I've found out that this is not possible in C# (please correct me if I'm wrong). After some Googling, I arrived at the conclusion that the only way to make a read-only object is to write an interface that only has 'get' properties and pass that in instead. Elegant, I must say.

public interface IFoo
{
  IMyValInterface MyVal{ get; }
}

public class Foo : IFoo
{
  private ConcreteMyVal _myVal;

  public IMyValInterface MyVal
  {
    get { return _myVal; }
  }
}

I would pass it into:

public void SomeFunction(IFoo fooVar)
{
  // Cannot modify fooVar, Excellent!!
}

This is fine. However, in the rest of my code, I would like to modify my object normally. Adding a 'set' property to the interface would break my read-only restriction. I can add a 'set' property to Foo (and not IFoo), but the signature expects an interface rather than a concrete object. I would have to do some casting.

// Add this to class Foo. Might assign null if cast fails??
set { _myVal = value as ConcreteMyVal; }

// Somewhere else in the code...
IFoo myFoo = new Foo;
(myFoo as Foo).MyFoo = new ConcreteMyVal();

Is there a more elegant way of replicating const or making read-only function parameters without adding another property or a function?

Pops
  • 30,199
  • 37
  • 136
  • 151
MarkP
  • 4,168
  • 10
  • 43
  • 84
  • 3
    Just to comment on your first sentence: that is not what "const" does. The referent is *still mutable*. Consider for example a method that takes two references to a type C, one marked const, one not. Pass a reference to the same variable for both references. If you mutate the non-const reference then the const reference will observe the mutation. const does **not** provide a guarantee of read-onlyness that you can rely upon. Nor does it prevent mutation of a mutable object, since it can be cast away. That is very different from readonly fields in C#. – Eric Lippert Sep 29 '10 at 23:28
  • 3
    To address your actual question: this is a confusing question. You seem to be saying that you want both (1) a guarantee of read-only-ness and (2) the ability to mutate the object. Those are opposites. You can't have both of them. Can you explain *why* you want SomeFunction to not be able to induce a mutation? Is this for *security* purposes? Is it for *correctness*? Who is providing the implementation of SomeFunction? If it's you, then why do you need it to be const? If you don't want to write a mutation, then don't. If it is someone else, then why do *you* care if they induce a mutation? – Eric Lippert Sep 29 '10 at 23:34
  • No it does not, but it does offer some protection within the scope of the function (unless you strip the const away via casting). – MarkP Sep 29 '10 at 23:35
  • 5
    @Eric: It makes sense to limit the capabilities of SomeFunction *even if I am the one writing it*. It increases compile-time checking to validate against accidental mistakes. (I’m sure you’ve made mistakes before — can you not imagine yourself making this kind of mistake where you accidentally mutate a variable when you weren’t supposed to?) I think the question asker doesn’t want a guarantee of read-only-ness, he only wants a semi-guarantee that *this method* won’t mutate *this object* when accessing it *through the parameter*. – Timwi Sep 29 '10 at 23:46
  • @Eric: Technically the person that implements SomeFunction() will not be me, rather, the 'client'. Therefore, I would like for him/her to have access to my data, but not be able to modify it. – MarkP Sep 29 '10 at 23:52
  • 1
    Merely making the interface provide read capability might not be sufficient. First: *is the client presumed to be actively hostile to you?* Is the person writing the "client" side of the application actively attempting to subvert the "service provider" side that you have implemented? Second: *is the hostile client fully trusted by the user running the client code? The security system is designed to protect *users*, not *software providers*. If you as a software provider need protection from hostile, full-trust users who want to subvert your code, then you have a hard problem to solve. – Eric Lippert Sep 30 '10 at 04:47
  • 3
    @Eric I think the idea here is **compile-time bug finding**, rather than security. I know that C# implements all the "traditional" ways to do this, but doesn't seem to be very receptive of the more recent approaches - like the ones being researched in Spec#. – Roman Starkov Sep 30 '10 at 09:11
  • 2
    @Eric: “Merely making the interface provide read capability might not be sufficient.” — For *security*, of course not! But for *some reassurance that I’ve not made a certain class of mistakes*, it is a pretty good start. Why does the “private” access modifier exist? It is clearly not *sufficient for security* because Reflection can subvert it. Does that mean it shouldn’t exist and we should all use “internal” instead? How are the *security characteristics* of “private” any different from “internal”? – Timwi Sep 30 '10 at 11:24
  • 1
    @Timwi: I'm attempting to determine the situation actually faced by the original poster. The OP states that the client should have read-only access to the data but not the consequences for failure of that policy. If the consequences of a hostile, fully-trusted client subverting the system to write the data are bad for the business model of the service provider then trivially wrapping a read-only wrapper around the data is insufficient. I'm not making any statement about the design of the access modifiers and their interaction with reflection and code access security in the CLR. – Eric Lippert Sep 30 '10 at 12:15
  • 1
    @Eric: Are you sure that you have fully entertained the possibility that the requested functionality may have nothing to do with security or hostile code? It may well be the case that all the OP is after is the kind of compile-time checking that *const* provides in C++, which he is familiar with and whose security limitations he is probably aware of. The same is true of people who request “protected and internal” and similar compile-time checking features on your blog: you seem to reject these ideas simply because they do not improve security against hostile code, but that misses the point. – Timwi Sep 30 '10 at 13:01
  • 1
    @Timwi: Of course I've entertained that possibility. I'm attempting to *confirm or eliminate* it by *asking focussed questions* to *determine the true scope of the problem*. (As for "proternal", I think you are mischaracterizing my position. It would be nice to have, and if we'd had it since v1, that would be fine with me. But "nice to have" doesn't make the bar for the expense of adding it now. Regardless, this is a distraction from the original poster's question which I am attempting to clarify.) – Eric Lippert Sep 30 '10 at 13:14
  • 1
    You can use `in` keyword. – Sebastian Xawery Wiśniowiecki Sep 15 '20 at 23:08
  • Related post - [Const function parameter in C#](https://stackoverflow.com/q/10981888/465053) – RBT Jan 13 '22 at 06:19

4 Answers4

13

I think you may be looking for a solution involving two interfaces in which one inherits from the other:

public interface IReadableFoo
{
    IMyValInterface MyVal { get; }
}

public interface IWritableFoo : IReadableFoo
{
    IMyValInterface MyVal { set; }
}

public class Foo : IWritableFoo 
{
    private ConcreteMyVal _myVal;

    public IMyValInterface MyVal
    {
        get { return _myVal; }
        set { _myVal = value as ConcreteMyVal; }
    }
}

Then you can declare methods whose parameter type “tells” whether it plans on changing the variable or not:

public void SomeFunction(IReadableFoo fooVar)
{
    // Cannot modify fooVar, excellent!
}

public void SomeOtherFunction(IWritableFoo fooVar)
{
    // Can modify fooVar, take care!
}

This mimics compile-time checks similar to constness in C++. As Eric Lippert correctly pointed out, this is not the same as immutability. But as a C++ programmer I think you know that.

By the way, you can achieve slightly better compile-time checking if you declare the type of the property in the class as ConcreteMyVal and implement the interface properties separately:

public class Foo : IWritableFoo 
{
    private ConcreteMyVal _myVal;

    public ConcreteMyVal MyVal
    {
        get { return _myVal; }
        set { _myVal = value; }
    }

    public IMyValInterface IReadableFoo.MyVal { get { return MyVal; } }
    public IMyValInterface IWritableFoo.MyVal
    {
        // (or use “(ConcreteMyVal)value” if you want it to throw
        set { MyVal = value as ConcreteMyVal; }
    }
}

This way, the setter can only throw when accessed through the interface, but not when accessed through the class.

Timwi
  • 65,159
  • 33
  • 165
  • 230
  • D'oh! That's EXACTLY what I have in my clipboard! – Phil Gilmore Sep 29 '10 at 23:40
  • This is a good solution. This is the same as creating two separate properties, one for set and one for get. Am I correct in assuming that for IWritableFoo.MyVal, I can use a concrete return type rather than an interface (so that I won't have to cast)?? – MarkP Sep 29 '10 at 23:56
  • Example: public interface IWritable { ConcreteMyVal MyVal { get; } } – MarkP Sep 29 '10 at 23:57
  • @user318811: Of course. Why don’t you just try it? (P.S. I assume you meant `set` instead of `get` in the last comment.) – Timwi Sep 29 '10 at 23:58
  • 2
    What stops the client from casting the IReadable to an IWriteable and writing away to their heart's content? – Eric Lippert Sep 30 '10 at 13:16
  • 3
    @Eric: What stops `Console.WriteLine` from formatting my hard disk? Nothing except for the fact that it tries to fulfill its contract. – Timwi Sep 30 '10 at 13:58
  • 3
    My point is that this is not actually using the type system as *enforcement* but rather as *polite suggestion*. If that's the goal then fine, you're done. But I and others are frequently in situations where polite suggestion is not enough; if the value actually needs to provide *no* mechanism whereby a mutation can occur via verifiable use of the type system then this is insufficient. It's the same reason why you can't just cast an array to IEnumerable and return it; someone could still mutate the array. If that is the situation the OP is in then an immutable wrapper object is better. – Eric Lippert Sep 30 '10 at 14:22
  • @Eric: nothing stops you from casting IWritableFoo to Foo. But do you necessarily know that Foo derives from IWritableFoo? – MarkP Sep 30 '10 at 20:30
  • @user318811: Call GetType() on the object and ask it to list all its interfaces. – Eric Lippert Sep 30 '10 at 21:34
  • 1
    @EricLippert: C# prevents methods from repointing their arguments to other objects by default unless we explicitly use `ref`. As a member of c# language designer, do you know why there is no simple mechanism such as providing us with a new keyword (something like C++ keyword `const`) to prevent methods from modifying the arguments' public states (fields, properties, events, etc) ? Using the above tricks with `interface` looks so complicated. – Second Person Shooter Mar 20 '20 at 07:05
  • 2
    @MoneyOrientedProgrammer: "const" is in my opinion a bad feature for a great many reasons; C# was designed to improve upon C++, and not copying this bad feature was a small improvement. I never want the feature "this method is restricted from modifying the state of this object via this reference". The feature I want is *this object's state does not observably change*, which is a very different feature. That's the one C# should add. – Eric Lippert Mar 20 '20 at 14:38
  • @EricLippert: I read somewhere that a good design should (or must?) not allow methods to have side effects. C# was designed to improve upon C++, but why does C# still have ref that allows methods to have side effect? const-like feature seems to be better than ref because it prevents methods from having side-effect. – Second Person Shooter Mar 25 '20 at 18:14
  • 3
    @MoneyOrientedProgrammer: A good design principle in C# is that a method *that produces a value* should be free of side effects because otherwise it becomes impossible to compute the value if you don't want the effect. Your belief that const prevents methods from having side effects is false, but lots of people believe it, and that's why const is a dangerously badly designed feature in my opinion. – Eric Lippert Mar 25 '20 at 19:00
  • 3
    @MoneyOrientedProgrammer: The problem that we run into is that there is a big difference between *observable effects* and *implementation detail effects*. Consider an expensive recursive algorithm marked as const, for example. We cannot *as a performance improvement* memoize the method within the class without removing the const, because memoization is by definition a side effect: it changes what is remembered. We end up having to jump through crazy hoops to maintain const correctness. – Eric Lippert Mar 25 '20 at 19:03
5

The closest equivalent is the in keyword. Using in makes the parameter and input parameter and prevents it from being changed inside the method. From the official C# documentation:

  • in - specifies that this parameter is passed by reference but is only read by the called method.
  • ref - specifies that this parameter is passed by reference and may be read or written by the called method.
  • out - specifies that this parameter is passed by reference and must be written by the called method.

enter image description here

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
3

First of all, you're correct: you cannot apply const or a similar keyword to parameters in C#.

However, you can use interfaces to do something along those lines. Interfaces are special in the sense, that it makes perfect sense to make an interface that only covers a specific part of a feature set. E.g. image a stack class, which implements both IPopable and IPushable. If you access the instance via the IPopable interface, you can only remove entries from the stack. If you access the instance via the IPushable interface, you can only add entries to the stack. You can use interfaces this way to get something similar to what you're asking for.

Brian Rasmussen
  • 114,645
  • 34
  • 221
  • 317
2

Consider Timwi's answer first. But as a second option, you could do this, making it more like the C CONST keyword.

Reference-type (object) parameters are IN parameters by default. But because they are references, their method side effects and property accesses are done to the object outside the method. The object doesn't have to be passed out. It has still been modified by the method.

However, a value-type (struct) parameter is also IN by default, and cannot have side effects or property modifications on the element that was passed in. Instead, it gets COPIED ON WRITE before going into the method. Any changes to it inside that method die when the method goes out of scope (the end of the method).

Do NOT change your classes to structs just to accommodate this need. It's a bad idea. But if they should be structs anyway, now you'll know.

BTW, half the programming community doesn't properly understand this concept but thinks they do (indeed, I've found inaccuracies on the matter of parameter direction in C# in several books). If you want to comment on the accuracy of my statements, please double check to make sure you know what you're talking about.

Timwi
  • 65,159
  • 33
  • 165
  • 230
Phil Gilmore
  • 1,286
  • 8
  • 15
  • It should be pointed out that using structs only has the effect of *discarding* changes to the object, which the compiler will happily allow. This means the author of the method can make changes to the object and then potentially get confused because they don’t persist. The intention of *const* in C++, and the interface-based solution in C#, is to send a signal that the method *is not supposed to* make changes and that the compiler should consider the attempt an error. – Timwi Sep 29 '10 at 23:57
  • Not sure why you go to such pains to highlight that all parameters are "in" by default, since you can't pass a value to modify using an "out" parameter anyway... Your distinction is between classes & structs and how they behave under pass-by-value - in/out has nothing to do with it as far as I can see. Is there a reason for your emphasis? – Dan Puzey Sep 30 '10 at 00:11
  • @Dan: I can’t speak for Phil, but I think it does make sense to point out that the statement “Any changes to it inside that method die” only applies if the parameter is not marked `ref`. – Timwi Sep 30 '10 at 14:01
  • @Timwi: Agreed, but that's nothing to do with "in" or "out". You can't pass a value in through an `out` parameter! By value versus by reference isn't the same as an `out` parameter. – Dan Puzey Sep 30 '10 at 16:05
  • @Dan: Well that’s not what the answerer meant. I believe he meant “in” (the default) as opposed to `out` or `ref` (which have to specified explicitly). You can’t pass a value in through an `out` parameter, but through a `ref` parameter you certainly can. – Timwi Sep 30 '10 at 18:11
  • @Timwi wrote, "*you can't pass a value in through an out parameter...*". Yes, but note that `out` vs `ref` distinction is a compile-time notion of the **C#** language only. The EMCA and CLR specs provide no built-in way to express the difference in a platform-wide manner. Obviously, since `ref` subsumes `out`, it is only `ref` that the specs formally define. Also somewhat obviously, C# never emits any proactive runtime enforcement (i.e., to police against *bad-faith* external callers by nuking-out incoming `out` values), since doing so can't possibly matter under the more restricted C# regime. – Glenn Slayden Feb 13 '21 at 23:36