Why not const ref params in C#?

  • Thread starter Thread starter My Name
  • Start date Start date
M

My Name

There was a topic on this earlier, but no answer, only people asking
"Why do you want to do this..."

Let's say I have a HUGE object and want to pass it to a routine that
only needs read access to it. It would sure be nice to declare the
receiving routine to be something like:

void DoSomething(const ref HugeThing) {
...
}

This way you get the performance benefit of a small reference being
passed, but the safety of knowing your object will be unchanged upon
return.

The point being that it is wasteful to make a copy of the object and
pass by value. It is also unsafe to just pass a reference, as the
called routine would then be free to trash your object.

Why is such simple and basic functionality missing from such a new
language?
 
I think that the basic concepts of const and ref are mutually exclusive. Do
you know of any other languages that support an equivalent to `const ref'?

I'm sure you've considered this already, but couldn't you just code your
DoSomething() not to modify HugeThing? :-)

Robert
 
The reference is constant but like you say the object's state is not.

public myMethod(myObject obj)
{
obj.SomeProperty = "changed"; // changes objects state
obj = new myObject; // obj is now a local object, when method ends the passed in obj reverts back to what you passed in
}

For fun I created a readonly object. First define a class like the following:

public class ReadOnly
{
bool isReadOnly = true;

public ReadOnly()
{ }

public ReadOnly(bool initialState)
{
isReadOnly = initialState;
}

public bool IsReadOnly
{
get { return isReadOnly; }
set { isReadOnly = value; }
}
}

Now define a class that you need to make read only. This class uses the above class to make itself read only.
public class Junk()
{
private ReadOnly isReadOnly;
private string data;

// creates a read/write object
public Junk(string theData) : this(theData, new ReadOnly(false))
{ }

// create an object with a given initial state
public Junk(string theData, ReadOnly initialState)
{
isReadOnly initialState;
data = theData;
}

public string Data
{
get { return data; }
set
{
Trace.Assert(!isReadOnly.IsReadOnly, "ReadOnly Object");
data = value;
}
}
}

In your code you create an instance of a ReadOnly object then pass to the constructor of your object that you wish to make read only. Because you control this object and there is no public access to it the called method cannot make any changes to it. You can create your object as writable, make all your changes then before passing it to another method you could mark it as readonly.

For example:

ReadOnly test = new ReadOnly(true);
Junk huge = new Junk("test", test);
TestReadOnly(huge); // method that tries to change a property on huge object, will fail if it tries to change the object's state

If TestReadOnly succeeds then it didn't try to change anything. You can then change the object to writable using:
test.IsReadOnly = false;
huge.Data = "changed";

I used Trace.Assert for dramatic effect. You could use any mechanism you'd like such as throwing an exception or just ignoring the change and do nothing.

Again just for fun. It's debatable if this is a good coding practice or not but it would work to ensure nobody screws with your object's state.
 
Robert Misiak said:
I think that the basic concepts of const and ref are mutually exclusive. Do
you know of any other languages that support an equivalent to `const ref'?
C++?

void myFunc(const BigClass &myBigClass);
A constant reference. Passes the actual object itself (as if by pointer) rather than making a copy, but it is constant. This is
one of the first things they taught me back in high school when studying recursion - how to avoid stack overflows ;)
I'm sure you've considered this already, but couldn't you just code your
DoSomething() not to modify HugeThing? :-)

What if it isn't YOUR DoSomething (aka you are writing a library that others use).
 
Following up on my comparison to C++...
The const reference works well there, as it maintains the low memory usage of only passing a reference, but the object (and its
members!) are still constant. In fact, the ONLY thing (AFAIK) that can be accessed when you have a const object like that are any
member functions (and maybe variables?) that are also declared const.

So:
class BigClass
{
int a;
BigClass() { a = 0;}

void CannotCallAsConst(int newA)
{
a = newA;
}

int CanCallAsConst() const
{
return a;
}

void TryToCallAsConst(int newA) const
{
a = newA; //COMPILATION ERROR
}
}

So, as you can see, everthing is still perfectly protected.

Now, apply this to C#. One big problem - methods cannot be declared const or readonly can they? In concept, it should be possible,
but the const or readonly keywords would have to be extended to apply to methods.
 
I think there's some confusion here that's causing some miscommunication.

Keep in mind that C++ and C# have different sematics for passing objects as
parameters to functions -- C++ will make a temporary copy of an object if
the argument type is not a reference (&) or pointer (*). C#, on the other
hand, will pass class instances by reference -- there's no way to make C#
pass class instances by value (except when remoting is involved).

Now, the original poster posted the C# declaration:

void DoSomething(const ref HugeObject)

which, I suspect, is not what he meant, since HugeObject will be passed by
reference automatically (unless it's a struct, but we'll assume that's not
the case). So, I think that's what Robert was pointing out when he said
const and ref don't really go together. What the original poster probably
meant was simply:

void DoSomething(const HugeObject)

which would presumably indicate an object passed by reference but which the
method will not modify. This would be essentially equivalent to following in
C++:

void DoSomething(const HugeObject&)

as mentioned by Adam. There's a thred in this newsgroup from earlier today
in which John Skeet talks about some of the issues of const correctness and
gives a link to the Rotor site:

http://constcli.sscli.net/

Ken


Adam Clauss said:
ref'?
C++?

void myFunc(const BigClass &myBigClass);
A constant reference. Passes the actual object itself (as if by pointer)
rather than making a copy, but it is constant. This is
one of the first things they taught me back in high school when studying
recursion - how to avoid stack overflows ;)
 
Wait... yeah, after reading Mattias's post realizing I've got things mixed up again. I don't think there is a need for it in C# -
you asked about a language it was possible in, and it is possible in C++, it just is not applicable here. Sorry :)
 
Yeah, I just realized that. Most of the time I understand how C# works, but sometimes I speak without thinking it through hehe :)
 
Now that you read all that nonsense... would I ever do anything like that? Very very doubtful.

I would
1. Design my objects to be read only if indeed I didn't want some other method mucking with them. Then allow changes only through method calls that I can control.
or
2. Create some sort of lightweight property bag that I could pass to the method.
or
3. Refactoring the huge object.

That's not to say my little example is without any use. If you had say some 20-25 different object types that needed to be mindful of some main state object then doing something like I have shown below could work nicely. Instead of trying to change the overall state on 25 objects you could just change the main state object.
 
In C#, *all* objects are passed by reference only, you cannot pass them by
value.
FYI, "ref" means that the reference is passed by reference which is
certainly not what you want.
There is no "const" for parameter passing in C# because it would make things
very complicates and objects should be encapsulated by themsolves making
"const" unneccesary.
 
In C#, *all* objects are passed by reference only, you cannot pass them
by
No, objects are not passed at all. References are passed by value by
default. There's a big difference between passing a reference by value
and passing a value by reference.

*lol*

It's always the same..

Passing objects by reference means to pass their reference by value. You
cannot pass any Object directly as an argument, you can only pass its
reference.
Arguments are *always* passed by *value* unless the parameter is marked with
the "ref" or "out" modifier.

I know you know that Jon this was just for clearing things up for anybody :)
 
cody said:
*lol*

It's always the same..

No, it's not.
Passing objects by reference means to pass their reference by value.

No it doesn't. Read some formal CS textbooks - or just the link I
posted before. Or any of the many discussions about this in the past -
I'm sure a groups.google.com search for my name and "pass by
reference" in this group will unearth plenty of threads. Pass-object-
by-reference and pass-object-reference-by-value *usually* end up having
the same effect, but not always. In particular, changing the value of
the formal parameter within the method doesn't have any effect on the
actual parameter outside the method with the latter, but would have
with the former.
You cannot pass any Object directly as an argument, you can only pass its
reference.
Yes.

Arguments are *always* passed by *value* unless the parameter is marked with
the "ref" or "out" modifier.
Yes.

I know you know that Jon this was just for clearing things up for anybody :)

I know the latter two points, but it appears that you don't quite know
the formal semantics of pass-by-reference vs pass-by-value.
 
In C#, *all* objects are passed by reference only, you cannot pass
them
No, it's not.


No it doesn't. Read some formal CS textbooks - or just the link I
posted before. Or any of the many discussions about this in the past -
I'm sure a groups.google.com search for my name and "pass by
reference" in this group will unearth plenty of threads. Pass-object-
by-reference and pass-object-reference-by-value *usually* end up having
the same effect, but not always. In particular, changing the value of
the formal parameter within the method doesn't have any effect on the
actual parameter outside the method with the latter, but would have
with the former.


Common, its all just termininology.

This has not very much to do with passing stuff to a method, this only has
to do with the fact that
structs have value semantics while objects haven't. If I assign a new struct
to a given struct I change its content, its value, which means if I pass a
struct to a method by reference and a I assign a new struct to it I change
the content of the original object.
Attempting to assign a new object to an object passed by reference value
doesn't change the object but only the reference variable laying on the
stack.

But this is all just syntax, at the end the only question is: is the
original object copied on the stack or is its reference copied on the stack
or is a refererence to a reference to it copied on the stack?
The first wouldn't allow any change to the passed object. The second allows
changed to the passed object while the last one allows even changes to the
variable containing the reference to the object which was passed to the
method.

1. pass by value
Foo(myStruct);

2. pass by reference
Foo(myObject);
Foo(ref MyStruct)

3. pass reference by reference
Foo(ref myObject);
 
cody said:
Common, its all just termininology.

Yes, but it's terminology that is well-defined - and which doesn't mean
what you think it does.
This has not very much to do with passing stuff to a method, this only has
to do with the fact that
structs have value semantics while objects haven't. If I assign a new struct
to a given struct I change its content, its value, which means if I pass a
struct to a method by reference and a I assign a new struct to it I change
the content of the original object.
Attempting to assign a new object to an object passed by reference value
doesn't change the object but only the reference variable laying on the
stack.

But this is all just syntax, at the end the only question is: is the
original object copied on the stack or is its reference copied on the
stack or is a refererence to a reference to it copied on the stack?

No, that's not the only question. There's the question of the type of
the parameter - is it a reference or is it a value type, and there's
the quite separate question of whether that is passed by value or by
reference, which affects what happens if the method changes the formal
parameter.
The first wouldn't allow any change to the passed object. The second
allows changed to the passed object while the last one allows even
changes to the variable containing the reference to the object which
was passed to the method.

1. pass by value
Foo(myStruct);
Yes.

2. pass by reference
Foo(myObject);
Foo(ref MyStruct)

No - the first is *not* pass-by-reference. It's pass-reference-by-
value, which has different semantics.

In pass-by-reference semantics, a change to the formal parameter within
the method is reflected in the actual parameter. In other words, if I
call

Foo(myObject)

and Foo contains a line of code saying:

myObject = new object();

then if myObject had been passed by reference, the calling code would
then see a different value of myObject. It doesn't, because the value
of myObject (whose value is a reference to start with, not an object)
is passed by value.
3. pass reference by reference
Foo(ref myObject);

Yes.
 
2. pass by reference
No - the first is *not* pass-by-reference. It's pass-reference-by-
value, which has different semantics.

Agreed. Theoretically we could also say that "Foo(myObject)" is passed by
value because the argument (the object reference) is -as it is- copied on
the stack.

However, I know what you mean and I agree on it and I (as a non native
english speaker) will certainly not fight with you about correct terminology
:)
In pass-by-reference semantics, a change to the formal parameter within
the method is reflected in the actual parameter. In other words, if I
call

Foo(myObject)

and Foo contains a line of code saying:

myObject = new object();

then if myObject had been passed by reference, the calling code would
then see a different value of myObject. It doesn't, because the value
of myObject (whose value is a reference to start with, not an object)
is passed by value.

I wouldn' disagree on that.
 
cody said:
Agreed. Theoretically we could also say that "Foo(myObject)" is passed by
value because the argument (the object reference) is -as it is- copied on
the stack.

There's no theoretically about it :)
However, I know what you mean and I agree on it and I (as a non native
english speaker) will certainly not fight with you about correct terminology
:)

It's not a case of English - it's a case of computer science. I've seen
experienced people who know what pass-by-reference *really* means get
very confused when they get told that C# or Java pass objects by
reference. It also makes for a more confusing mental model than passing
a reference by value, as the latter is entirely consistent with the
rest of C# - assignment, type of expression, etc.
 
Back
Top