Posted By: VBJB | Jun 14th, 2007 @ 5:15 PM
page 1 of 1
Comments: 20 | Views: 6528

Can someone explain to me why you cannot pass an object/variable into a method as const ref in C#/.net or am I beating a dead horse?

What is the most efficient way to pass in large objects into a method and be reassured it will not be changed in the method? I do not want to pass by value.

I did some searching online and could not find and technical papers on why const ref is not a part of .net.

Thanks for your input.

Declaring as 'static' won't help you?
const needs to be fully evaluated at compile time and needs to be a type that can be implicitly converted... hence the no-go on object.

If you are simply trying to preserve the inital state of an object passed to you, perhaps you could create a new copy when you receive it ?
No, there's no equivalent to a const ref in C#.

That said, there's a fairly simple way to get around it: interfaces. Create an interface for your object that does not allow modifications of the object, and pass the object cast as that interface to the method, rather than the whole object itself. The calling method cannot then directly modify the original object.

NOTE: With .NET reflection all bets are off. Even private methods are fair game with reflection.

So it basically boils down to trust. Do you trust the method you're calling not to modify your object?
I don't understand.

Why pass it as ref if you don't want it changed in the first place?

Perplexed
Or try to provide a function that use StackTrace to attempt to find the immediate caller, if it's empty or an "recognized" one then allow write, deny otherwise.

Btw, it'd be too heavyduty to do this on every "set methods" and may take more time to execute then just have the object be passed by value.
phreaks wrote:
I don't understand.

Why pass it as ref if you don't want it changed in the first place?


Because it takes time and space to copy a large object. Imagine if the method will be called in tight loop... I bet the GC won't like it(quick allocation and free), or will it?

Passing by ref don't create duplicates.
cheong wrote:

phreaks wrote: I don't understand.

Why pass it as ref if you don't want it changed in the first place?


Because it takes time and space to copy a large object. Imagine if the method will be called in tight loop... I bet the GC won't like it(quick allocation and free), or will it?

Passing by ref don't create duplicates.


That only applies to value types (structs). In .Net, reference types (classes) are never copied. Adding the ref keyword for a reference type means you are passing the reference by reference.

Even when you are using value types the GC doesn't come into play, since value types always live on the stack and are not garbage collected.

The problem with allowing const parameters is that you must then also have const methods like in C++. Without this, the compiler wouldn't be able to guarantee that a property or method call wouldn't change the object. Over the years it has been discovered that there are some disadvantageous to having two different classes of code (const and non-const) where one can't call the other, which is probably why the CLR/C# teams didn't put this in.

Randolpho wrote:
That said, there's a fairly simple way to get around it: interfaces. Create an interface for your object that does not allow modifications of the object, and pass the object cast as that interface to the method, rather than the whole object itself. The calling method cannot then directly modify the original object.


Using an interface that provides only read-only access wouldn't work, since the function can always cast it back to the original object. The only thing that really works is a read only wrapper, which is indeed what happens when you use e.g. List<T>.AsReadOnly().

Sven Groot wrote:

That only applies to value types (structs). In .Net, reference types (classes) are never copied. Adding the ref keyword for a reference type means you are passing the reference by reference.

Oops. I've forgotten that detail. Tongue Out

Sven Groot wrote:

Using an interface that provides only read-only access wouldn't work, since the function can always cast it back to the original object. The only thing that really works is a read only wrapper, which is indeed what happens when you use e.g. List<T>.AsReadOnly().


Learnt something new today. Big Smile

One more reason for not having "const" behavior on methods is that the CLS restricts the range of capabilities of the CLR to support a wide range of languages. In this case, not each of languages supports const methods and parameters, and they couldn't work with code written in language which supports and uses them. Btw, Managed C++ supports const methods and parameters, as it has done for many years before .Net, but ONLY with C++ code. Actually, C++ compiler adds custom attributes which indicate usage of const modifier, but only C++ compiler is aware of this attribute.
Sven Groot wrote:
Using an interface that provides only read-only access wouldn't work, since the function can always cast it back to the original object.


I'm nearly C++-illiterate, but I remember it said that even in C++ you can cast away the const into a non-const reference, making it pretty much equivalent to passing an interface: it's there to prevent accidental mistakes, not neutralize code that actively tries to corrupt data. For untrusted code, cloning your object is the best option.
Yggdrasil wrote:

Sven Groot wrote: Using an interface that provides only read-only access wouldn't work, since the function can always cast it back to the original object.


I'm nearly C++-illiterate, but I remember it said that even in C++ you can cast away the const into a non-const reference.

Yes it can, but the results of trying to write to an object whose const-ness was casted away is undefined by the standard, so it's up to the compiler to decide what happens. It's therefore not a very safe thing to do.
cheong wrote:

Sven Groot wrote: 
That only applies to value types (structs). In .Net, reference types (classes) are never copied. Adding the ref keyword for a reference type means you are passing the reference by reference.

Oops. I've forgotten that detail.

Sven Groot wrote: 
Using an interface that provides only read-only access wouldn't work, since the function can always cast it back to the original object. The only thing that really works is a read only wrapper, which is indeed what happens when you use e.g. List<T>.AsReadOnly().


Learnt something new today.



Talking of lists anyway, you should only use them as the working object, if you ever need to return a generic list of items, you should return a generic collection, the AsReadOnly method is most likely only calling something like:

return new ReadOnlyCollection<T>(this);

Same again if you are collating data inside a method to return, you shouldn't return the generic list directly, instead return a new generic collection by sending the list as the ctor arg.
Sven Groot wrote:
Using an interface that provides only read-only access wouldn't work, since the function can always cast it back to the original object. The only thing that really works is a read only wrapper, which is indeed what happens when you use e.g. List<T>.AsReadOnly().


Excellent point!

But remember, with reflection the wrapper class can essentially be unwrapped, as the calling method can simply access the original object through the wrapper's private storage.

So, VBJB, the answer is this: You cannot get a const reference in C#, period, because the runtime lets you access pretty much anything. Also, as has already been mentioned, even with C++ most compilers have workarounds that essentially let you cast away constness.

It boils down to trust. Do you trust the calling method not to mess with your object?

How about implementing ICloneable and then passing in a clone?

nlondon wrote:


How about implementing ICloneable and then passing in a clone?



Explicitly forbidden by the original post -- the object is too large to be copied/cloned
Sven Groot wrote:


That only applies to value types (structs). In .Net, reference types (classes) are never copied. Adding the ref keyword for a reference type means you are passing the reference by reference.



So are you saying that when we are passing by value we are really passing the *address* of the object by value?

If this is true, then passing big objects by value is not that bad, but I want to clarify this first.

Someone?
VBJB wrote:


So are you saying that when we are passing by value we are really passing the *address* of the object by value?

If this is true, then passing big objects by value is not that bad, but I want to clarify this first.

Someone?


Yes.

SomeReallyHugeClass obj = new SomeReallyHugeClass();

DoSomeWork(obj);

//-----

void DoSomeWork(SomeReallyHugeClass p)
{
   // p is passed "by value" as C-style pointer

   p.SomeProperty = "something else";  // perfectly legal

   p = new SomeReallyHugeClass();  // won't compile
}

void DoSomeMoreWork(ref SomeReallyHugeClass p)
{
   p = new SomeReallyHugeClass();  // Now, it's OK
}
VBJB wrote:

Sven Groot wrote: 

That only applies to value types (structs). In .Net, reference types (classes) are never copied. Adding the ref keyword for a reference type means you are passing the reference by reference.



So are you saying that when we are passing by value we are really passing the *address* of the object by value?

If this is true, then passing big objects by value is not that bad, but I want to clarify this first.

Someone?


It still doesn't prevent the callee from modifying your object. The object *reference* is passed.

Minh, your "won't compile" part will compile fine. It's a local change to the parameter.
VBJB wrote:
So are you saying that when we are passing by value we are really passing the *address* of the object by value?

If this is true, then passing big objects by value is not that bad, but I want to clarify this first.

Someone?


No, you've got it wrong. I think you're still thinking about C++. In C++ a type is a type, and it can be instantiated on the stack or the heap, depending on how you instantiate it. In C# (and .NET in general), however, there are two different types of objects -- reference types and value types. Here's a short tutorial:

http://www.albahari.com/value%20vs%20reference%20types.html

Basically, a reference type is instantiated on the heap and a reference (similar to a pointer, but no pointer arithmetic) to the object is stored in the stack, while a value type is instantiated on the stack. When you pass a reference type to a method, you're passing that pointer equivalent to the method. When you pass a value type to a method, a copy is pushed onto the stack and the method uses the copy. The ref and out keyword when applied to a value type are like a pointer to the type, but when applied to a reference type, they're equivalent to a pointer to a pointer.

Let me see if I can explain the differences in terms of C++ and C#:

Reference Type
In C#, a reference type is instantiated as follows:
MyObject foo = new MyObject();
The C++ equivalent:
MyObject * const foo = new MyObject();
In C++ a reference type is equivalent to a const pointer to an object. It's much safer than a pointer, however.

In C# a reference type is declared in a method as follows:
void MyMethod(MyObject foo) {...}
The C++ equivalent:
void MyMethod(MyObject * const foo) {...}
Value Type
In C#, a value type is instantiated as follows:
MyStruct foo; // uses default constructor
// or
MyStruct foo = new MyStruct(); // also uses default constructor
The C++ equivalent:
MyStruct foo; 
// or
MyStruct foo(arg1, arg2);
In C++ a value type is equivalent to a stack instantiated type. In C++, any type can be stack instantiated. In C#, only value types (i.e. ints, floats, bools, and structs) can be stack instantiated.

In C# a value type is declared in a method as follows:
void MyMethod(MyStruct foo) {...}
The C++ equivalent:
void MyMethod(MyStruct foo) {...}
Again, when passing a value type, a shallow copy is pushed to the stack.

ref and out Keywords
In C#, adding the ref or the out keyword to a method parameter is very similar to adding an additional pointer to the parameter. If you add ref to a value type, it's like passing a pointer to a stack-instantiated object. If you add it to a value type, it's like passing a pointer to a const-pointer to a heap-instantiated object.

In C# a ref to a value type is declared as follows:
void MyMethod(ref MyStruct foo) {...}

The C++ equivalent:

void MyMethod(MyStruct * foo) {...}

In C# a ref to a reference type is declared as follows:
void MyMethod(ref MyObject foo) {...}
the C++ equivalent:
void MyMethod(MyObject * * foo) {...}
As Minh mentioned, you can use the ref keyword to re-instantiate a reference type.


So, where does that leave you? Well, if your object is a reference type object -- even a very large one -- you don't have to do anything special -- reference types are automatically passed by reference without needing the ref or out keyword.

You worried about the calling method being able to modify your object. Frankly, the bottom line is you have no real control over whether or not the calling method modifies your object; if you give it your object any method has the ability to modify it. You can make it harder to modify with tricks -- one such trick that you're used to in C++ is passing a const pointer, and you can do a similar trick in C# using a read-only interface -- but ultimately any sufficiently determined method can modify your object.

It boils down to trust. If you control the method code, you don't have to worry, just don't modify the object. Badabingbadaboom. If you don't control the method code, you have to determine your level of trust for the method.
The only thing that really works is a read only wrapper, which is indeed what happens when you use e.g. List<T>.AsReadOnly().


NO!  That isn't right at all!

If you have an class such as:

public class StrangePair {

   public int i;

   public double d;

}


And you make a list of them:

List<StrangePair>

and populate the list with 100 items.

Then you get the readonly version:
IList<StrangePair> RoList = List<StrangePair>.AsReadOnly();

All that means is you can't do things like this:
RoList[2] = new StrangePair();

But you CAN DO THIS:
RoList[2].i = 99;

You have CHANGED part of what was passed!

The original question wanted an equivalent to a C++ type of const where the internal values of the object can NOT be changed.  The C# read only concept only says you can't point to a new object... but any object you have you can modify any way you want.  (And all of the documentation is misleading because all the examples use strings, which are a highly specialized class and shouldn't be used for ANY examples because no standard class acts like strings.)

Also as for motivation... it isn't a matter of trust.  Yes, you can work around almost anything... and there ARE built in work arounds for C++ const, and I have used them.  Sometimes it is APPROPRIATE to use them... but it is extremely rare.  What one DOES protect against is an ACCIDENT or OVERSIGHT or UNINFORMED user.  If you go out of your way to work around something, it is pretty obvious in the code.  Mistakes are rarely obvious.

Bottom line... don't go to the trouble of producing an answer if it is going to be wrong.  You might just as well kick out random numbers.