tags:

views:

122

answers:

3

Coming from a c++ background, I'm used to sticking the const keyword into function definitions to make the object being passed in a read-only value. However, this is not possible in C#, as I found out (please correct me if I'm wrong). So, after some googling, I came to 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. If I add a 'set' property to the interface, this will break my readonly 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 making 'const' or readonly function parameters without adding another property or a function??

A: 

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
+2  A: 

I think you may be looking for a solution involving two interfaces that inherit from each 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
D'oh! That's EXACTLY what I have in my clipboard!
Phil Gilmore
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)??
Example: public interface IWritable { ConcreteMyVal MyVal { get; } }
@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
What stops the client from casting the IReadable to an IWriteable and writing away to their heart's content?
Eric Lippert
@Eric: What stops `Console.WriteLine` from formatting my hard disk? Nothing except for the fact that it tries to fulfill its contract.
Timwi
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<T> 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
@Eric: nothing stops you from casting IWritableFoo to Foo. But do you necessarily know that Foo derives from IWritableFoo?
@user318811: Call GetType() on the object and ask it to list all its interfaces.
Eric Lippert
A: 

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.

Phil Gilmore
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
Dan Puzey
@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
@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
@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