views:

2007

answers:

8

I'm a heavy C++ user who dabbles in C# in his spare time. I'm also one of those const-correctness nazis and so not being able to do this easily in C# grates a little.

The point of const-correctness is to be able to provide a view of an instance that can't be altered or deleted by the user. The compiler supports this by pointing out when you break constness from within a const function, or try to use a non-const function of a const object. So without copying the const approach, is there a methodology I can use in C# that has the same ends?

I'm aware of immutability, but that doesn't really carry over to container objects to name but one example.

+2  A: 

C# doesn't have such feature. You can pass argument by value or by reference. Reference itself is immutable unless you specify ref modifier. But referenced data isn't immutable. So you need to be careful if you want to avoid side effects.

MSDN:

Passing Parameters

aku
well I guess that's what the question comes down to then: what's the best way to avoid the side effects of not having a const structure?
tenpn
Unfortunately only immutable types can help it. You can have a look at Spec# - there are some interesting compile time checking.
aku
+2  A: 

There's a nice discussion about possible designs for readonly at http://blogs.msdn.com/brada/archive/2004/02/04/67859.aspx

Asaf R
+16  A: 

To get the benefit of const-craziness (or pureness in functional programming terms), you will need to design your classes in a way so they are immutable, just like the String class of c# is.

This approach is way better than just marking an object as readonly, since with immutable classes you can pass data around easily in multi-tasking environments.

Sam
but immutability doesn't really scale to complex objects, or does it?
tenpn
I'd argue that if your object was so complex that immutability was impossible, you'd have a good candidate for refactoring.
Jim Burger
I think this one is the best of the group. Immutable objects are used too infrequently.
Will
Not only that, but there are cases where you do want to have changing objects (objects do change!) but still offer a read only view in most cases. Immutable objects imply that whenever you need to make a change (change happens) you will need to make a new object with all the same data besides the change. Consider a school that has school rooms, students... do you want to create a new school each time a student's birthdate goes by and her age changes? Or can you just change the age at the student level, the student at the room level, maybe the room at the school level?
David Rodríguez - dribeas
@tenpn: Immutability actually scales incredibly well, when done right. One thing that really helps is the usage of `Builder` classes for large immutable types (Java and .NET define the `StringBuilder` class which is just one example).
Konrad Rudolph
+1  A: 
  • The const keyword can be used for compile time constants such as primitive types and strings
  • The readonly keyword can be used for run-time constants such as reference types

The problem with readonly is that it only allows the reference (pointer) to be constant. The thing referenced (pointed to) can still be modified. This is the tricky part but there is no way around it. To implement constant objects means making them not expose any mutable methods or properties but this is awkward.

See also Effective C#: 50 Specific Ways to Improve Your C# (Item 2 - Prefer readonly to const.)

Thomas Bratt
+27  A: 

I've come across this issue a lot of times too and ended up using interfaces.

I think it's important to drop the idea that C# is any form, or even an evolution of C++. They're two different languages that share almost the same syntax.

I usually express 'const correctness' in C# by defining a read-only view of a class:

public interface IReadOnlyCustomer
{
    String Name { get; }
    int Age { get; }
}

public class Customer : IReadOnlyCustomer
{
    private string m_name;
    private int m_age;

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        set { m_age = value; }
    }
}
Trap
This isn't one of those questions that has an "answer" as such, but I really like this solution. It's elegant, can be made to fit a lot of situations, and is quick to write.
tenpn
Sure, but what if one of your fields is a List, or a rich type. Your solution gets complicated very quickly.
Matt Cruikshank
Returning an internal collection is considered a bad practice. You either return a read-only interface to manage that particular read-only list or a copy of the original list so no harm can be done.
Trap
@Trap: that is the whole point of the question. Returning an internal collection is a bad practice because the outside world could modify your internals, in C++ that is solved with the use of const: you provide a constant view of the collection (cannot add/remove elements, only offers a constant view of its elements) and as such it is safe (and a common idiom). No need to work out interfaces or other tricks to avoid outside code from changing your internals.
David Rodríguez - dribeas
@Matt: Agree, the nice part of const correctness is that it is 'transitive' in a sense. You provide a constant reference to a container that itself takes care of only providing constant views of its elements, each of which only offers constant views of its inner elements...
David Rodríguez - dribeas
Do remember that this is only a cast away from removing the read-onli-ness - namely, `(Customer) myCustomer`. Still, probably the best solution, if you behave and don't cast like that :)
romkyns
@David: Languages provide these kind of mechanisms as an aid to development, not as a means to avoid inexperienced/bad programmers to do things the wrong way. In C# you achieve this in a natural way just by using interfaces, while in C++ you need an 'extra' feature called 'const'. It always looked to me that C++ is the one using a 'trick', not the other way around.
Trap
@David: Also, by using a read-only interface you're explicitly declaring your intentions, it's a design decision. I can't remember how many times I screwed things up due to forgetting to add the 'const' keyword.
Trap
@Trap, I agree that the mechanism is an aid for development which does include detecting errors from developers, experienced or not. I disagree that having to write read-only interfaces is a better solution in several grounds. The first point is that whenever you are writting a class in C++ you are implicitly defining the constant interface: the subset of members declared `const`, without the extra cost of defining a separate interface and requiring runtime dispatch. That is, the language provides a simple way of implementing compile-time const-interfaces.
David Rodríguez - dribeas
It is important to note that the `const`-ness **is** part of the design and it declares the intention just as clearly as writting an external const-interface. Also, providing const-interfaces can be a complex task if it has to be tackled manually, and might require writting almost as many interfaces as classes are present in the compound type. That takes us to your last sentece: 'screw things up due to forgetting to add the const'. It is easier to get used to add the `const` everywhere (development cost is small) than to write read-only interfaces for every class.
David Rodríguez - dribeas
@David: I'm not against the C++ 'const' keyword :) I just like the way const-correctness is achieved in C# more. I think that the amount of words you have to type to get things done can never be an important factor. As a note, in every modern language/IDE that I know you can extract a read-only interface with a few mouse clicks.
Trap
If you hold a big enough mutable object inside your own object, how will you provide a read-only interface to the containing object? Will you create a copy of the contained object? This is something you only miss when you learn how powerful having const-references is.
David Rodríguez - dribeas
As I mentioned in comment #3, and if size really matters, the container can provide a special read-only interface to handle that particular and insanely big object.
Trap
+5  A: 

Interfaces are the answer, and are actually more powerful than "const" in C++. const is a one-size-fits-all solution to the problem where "const" is defined as "doesn't set members or call something that sets members". That's a good shorthand for const-ness in many scenarios, but not all of them. For example, consider a function that calculates a value based on some members but also caches the results. In C++, that's considered non-const, although from the user's perspective it is essentially const.

Interfaces give you more flexibility in defining the specific subset of capabilities you want to provide from your class. Want const-ness? Just provide an interface with no mutating methods. Want to allow setting some things but not others? Provide an interface with just those methods.

munificent
Not 100% correct. C++ const methods are allowed to mutate members marked as `mutable`.
Constantin
Fair enough. And const-casting lets you get rid of const-ness. Both kind of imply that even the C++ designers realized one-size-fits-all is really one-size-fits-most.
munificent
The advantage of C++ is that you have const for most cases, and you can implement interfaces for others. Now, besides const, C++ has the mutable keyword to apply to attributes as caches of data, or locking mechanisms (mutexes or the like). Const is not 'will not change any internal attribute) but rather will not change the object state as perceived from outside. That is, any uses of the object before and after calling a const method will yield the same result.
David Rodríguez - dribeas
casting away const to mutate comething in C++ is undefiend behaviour (nasal demons). const_cast is for interfacing with legacy code that while being logically const isn't marked const.
jk
+9  A: 

I just wanted to note for you that many of the System.Collections.Generics containers have an AsReadOnly method which will give you back an immutable collection.

Rick Minerich
+2  A: 

Agree with some of the others look at using readonly fields that you initialize in the constructor, to create immutable objects.

    public class Customer
    {
    private readonly string m_name;
    private readonly int m_age;

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
    }

    public int Age
    {
        get { return m_age; }
    }
  }

Alternatively you could also add access scope on the properties, i.e. public get and protected set?

    public class Customer
    {
    private string m_name;
    private int m_age;

    protected Customer() 
    {}

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
        protected set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        protected set { m_age = value; }
    }
  }
Stuart McConnell
These are different approaches that do not really fit the whole problem. With access control level you only allow deriving classes from changes, in many cases this will not model the real world appropriatedly. A teacher does not derive from a student record but may want to change the student grades, although the student cannot change the grades but can read them... just to name a simple example.
David Rodríguez - dribeas