Posted By: JeremyJ | Feb 15th, 2007 @ 10:05 AM
page 1 of 1
Comments: 11 | Views: 8240
JeremyJ
JeremyJ
The pioneers would be appalled!
I am having a strange problem that maybe someone can answer for me.  I have 2 classes: Recipient and Contact.

    public class Contact

    {

    }

    public class Recipient : Contact

    {

    }


If I try to pass Recipient into a function that accepts a Contact reference like this:

   Recipient data = new Recipient();

   FetchContact( ref ( Contact )data, row );


It throws a error:
A ref or out argument must be an assignable variable

If I do this then it accepts it just fine:

    Recipient data = new Recipient();

    Contact contact = ( Contact )data;

    FetchContact( ref contact, row );


The error message usually happens when you try to pass a constant by reference.  Am I missing something?

(Why does it keep double spacing everything I post?)

littleguru
littleguru
<3 Seattle
This is how the ref keyword works. You need to have a "pointer" to the actual object that is passed in. And it needs to exist as variable in the function (method) that calls the function (method) with the ref parameter...

You need to allocate a temporary variable that holds the casted value.
amotif
amotif
No Silver Bullet
FWIW, you don't need the cast in your solution:

JeremyJ wrote:
If I do this then it accepts it just fine:

    Recipient data = new Recipient();

    Contact contact = ( Contact )data;

    FetchContact( ref contact, row );



Like lg mentioned, the method takes a reference to a Contact, not a reference to a Recipient. If what you first tried to do actually worked FetchContact could return any type derived from Contact in your Recipient reference. That's not type-safe.

In short, your initial solution assumed that FetchContact was returning a Recipient, but there's nothing in FetchContract's signature that indicates this.
littleguru
littleguru
<3 Seattle
JeremyJ wrote:
The only difference between the two blocks of code is the fact that I am creating a second pointer and passing that in.
The Contact contact = ( Contact )data; is doing a shallow copy and not a deep copy.


Well this does not a copy, it does only a type cast... Makes sure that the memory block is a Contact.
Sven Groot
Sven Groot
My name has 9 letters. Coincidence? I think not...

JeremyJ wrote:
The two blocks of code do the same thing.  I can do the equivilent in other languages and it works just fine (yes I have tested this).

They don't do entirely the same thing. What you want to do would actually be equivalent to this (sort of):
Recipient data;
Contact contact = data;
FetchContact(ref contact, row);
data = (Recipient)contact;

It's the final line that makes the difference. Let's look at the following example:

class Base { }
class Child1 : Base { }
class Child2 : Base { }

void FetchBase(ref Base base)
{
   /* Omitted: do something with original value of base. */

  base = new Child2();
}

Now if you do what you're describing above, it'd look like this:
Child1 c = new Child1();
FetchBase(ref (Base)c);

You'd end up trying to assign a Child2 object to a Child1 variable! It can't be done.

That's what Amotif was getting at.

JeremyJ wrote:
The only difference between the two blocks of code is the fact that I am creating a second pointer and passing that in. The Contact contact = ( Contact )data; is doing a shallow copy and not a deep copy.

No, the difference is that when FetchContact changes the value of the ref parameter, it's assigning to a variable of type Contact, whereas your first version would assign to a variable of type Recipient while it thinks it's assigning to one of type Contact.

And there's no copying done at all, besides the reference. A shallow copy would be a new Recipient object but with the same members; and where they are references, the new object gets the same reference (a deep copy would also clone the referenced objects from the members).

amotif
amotif
No Silver Bullet

littleguru and Sven have pretty much covered it. I've blathered on about it on my blog. Hopefully we've said enough. Smiley

JeremyJ wrote:
The two blocks of code do the same thing.  I can do the equivilent in other languages and it works just fine (yes I have tested this).


Yes, you can do this in languages that allow you to subvert type safety IFF you're lucky about class layouts or you're making (currently) correct assumptions about what FetchContact returns. If any of that changes your luck has run out.

JeremyJ wrote:
I have no idea what you are talking about amotif because you do need to do the cast and it is type safe.


If you try it you'll find that a cast is not necessary for

    Derived d = new Derived();
    Base b = d;

Hopefully what we've written about it will soon help you think otherwise about it being type safe.

Sven Groot
Sven Groot
My name has 9 letters. Coincidence? I think not...
JeremyJ wrote:
The last line you added to the code isn't needed.
data = (Recipient)contact;

contact is already pointing to data so data gets updated automatically.  I tested that in the code to verify it was correct.

Wait, so the parameter reference value isn't being changed? Then why is it a ref parameter? That last list is definitely needed if you use ref parameters for their intended purpose.

JeremyJ wrote:
As for changing the derived type in the function, you are correct, there is no one stopping anyone from writing bad code.  Not sure what that had to do with the question though.

There's two ways to deal with this.
1. Don't allow it.
2. Throw an InvalidCastException at run time when such an invalid assignment occurs.

The C# team chose the former. That's the problem you're hitting and that's why it was relevant.
Sven Groot
Sven Groot
My name has 9 letters. Coincidence? I think not...
JeremyJ wrote:
The parameter reference is being changed. 
The contact variable is pointing to data.  So when contact is changed data is changed.  That is just the way pointers work.  Give it a try and you will see what I am talking about.

No. All reference types in .Net are passed by reference anyway. Only when you want to return a different reference through the parameter is a ref parameter required.

void Foo(Bar b)
{
   b.Baz = 5;
}
If Bar is a class (and not a struct), no ref parameter is required for this, and Foo's caller will still see the change in Baz.

void Foo(ref Bar b)
{
   b = new Bar();
}
That's what a ref parameter is for (and actually, since I'm not using the value passed in, I should use an out parameter).

The C++ equivalent would look like this:
void Foo(Bar *b)
{
   b->Baz = 5;
}

void Foo(Bar *&b)
{
   b = new Bar();
}

When dealing with reference types, you're already passing a pointer even without ref, so you can change the instance (but not the pointer itself). With ref, you're actually passing a reference to a pointer. This is only necessary when you want to change the pointer itself.
page 1 of 1
Comments: 11 | Views: 8240
Microsoft Communities