The deal with value and reference types

  • Thread starter Juan Gabriel Del Cid
  • Start date
J

Juan Gabriel Del Cid

public static CVector operator - (CVector v, CVector v2)
{
return new CVector(v.x-v2.x, v.y-v2.y, v.z-v2.z);
}

What's wrong with:

public static CVector operator - (CVector v, CVector v2) {
v.x = v.x - v2.x;
v.y = v.y - v2.y;
v.z = v.z - v2.z;

return v;
}

This is the same as your C++ implementation. I don't think the minus
operator should modify any of it's operands, though. This is a terible
design.

-JG
 
J

Juan Gabriel Del Cid

INCORRECT
---------
Line 1: int i = 5;
Line 2: object o = (object)i;
Line 3: i = 10;
Line 4: Console.Write("{0},{1}",i,(int)o);

Result: 10, 5

I can understand why this happens though, when I do "i = 10", the compiler is
internally doing "i = new System.Int32(10)", so the reference gets lost, as
it's allocating new memory.

Nope, you got it all wrong. In line 2, the compiler intenally allocated
memory for the boxed integer and copied it's value there. Like this:

Stack Heap
+-------+ +------+
i | 5 | | ... |
+-------+ +------+
o | ref --+--------> | 5 | // this is the boxed integer
+-------+ +------+

And yes, you're right, the ony way to reference to a value type is through
(unsafe) pointers.

Hope that helps,
-JG
 
J

Javier Campos

WARNING: This is an HTML post, for the sake of readability, if your client can see HTML posts, do it, it doesn't contain any script or virus :)
I can reformat a non-HTML post if you want me to (and if this doesn't see correctly with non-HTML viewers)

Ok, I'm fed up with this so I'll explain the situation here and my approach (which sucks), and see if anyone can give a better solution to this.

I'm making a way to have several parameters of a class (a instance of a class) accesed via strings (for a script system)... I don't want to use the default property collections in C# because: 1) that'd give me access to all public properties/member variables, and that's not what I want, and 2) that'd not be easy to manage from other languages.

So that doesn't count as an option.

My current approach is:

- I have an interface IScriptable, where I define some methods that all classes with accesable as scripting components will have (basically, a function that will set the parameters and associated values)

- I have a base class, CScriptableObject (which implements IScriptable) with the basic implementation of this class, with no parameters. Rests of the classes will derive from this, and override certain methods.

- Let me make an example of a CScripttableObject derived class:

/*
Color Chart (for those of you with an HTML-viewer for usenet:

Blue: normal 'blue' stuff in Visual Studio.Net
Green: Comments
Purple: Variables that will be accessed from the Scripting Engine
Red: Name of the variables in the script (example, in a script, var1 will access variable1)
Black: Rest of the code
*/

public class MyScriptableClass : CScriptableObject
{
public MyType variable1, variable2;
private OtherType someMoreVariables;

// This is overriden from the CScriptableObject's one
public override void InitializeMembers()
{
base.InitializeMembers(); // We'll call the parent class first, just in case
// initialization of variables
someMoreVariables = new OtherType();
variable1 = new MyType();
variable2 = new MyType();
}

// This is overriden from the CScriptableObject's one
public override void SetObjectInfo()
{
ScriptObject = new ScriptObjectInfo
( "MyTypeClass", // String name for this class type [for the scripting engine]
new ScriptParameter("var1", ref variable1), // We set a parameter, named "var1", referencing variable1
new ScriptParameter("var2", ref variable2) // We set a parameter, named "var2", referencing variable2
);
}

// [etc...]
}


Ok, so now we have that we have a class "MyType", with 2 script-accesible variables, "variable1" and "variable2". The script-name for the class type will be "MyTypeClass", and the variable names (for the script) are "var1" and "var2". There are other variables in the class (someMoreVariables) which are not accesible through the script (as they are not defined for the script in the ScriptObjectInfo).

Now in the base CScriptableObjects I have some methos to retrieve parameters.

Ok, I had this implemented in C++, using pointers and stuff, and there was not a single problem with it. Now I've come to this problem in C#

I can use a reference to reference-types, or "boxed" value-types, and store it in an "object" class. HOWEVER:

- If I ever assign (within the class) a value to the value-type, the reference is referencing a copy, thus doesn't get updated:

Example:
CORRECT
-------
int i = 5;
object o = (object)i;
o = 10;
Console.Write("{0},{1}",i,(int)o);

Result: 10, 10

INCORRECT
---------
int i = 5;
object o = (object)i;
i = 10;
Console.Write("{0},{1}",i,(int)o);

Result: 10, 5

I can understand why this happens though, when I do "i = 10", the compiler is internally doing "i = new System.Int32(10)", so the reference gets lost, as it's allocating new memory.

Now there are only two workarounds for this:

1) Make a wrapper class for value types.
This leads me to two problems:

- You can't overload operator =, and the only way to do it is setting an implicit operator, for example:

public static implicit operator CIntParameter(int val)
{
return new CIntParameter(val);
}

- That'd allow me to do:
CIntParameter i = 10;

However, that creates a new object anyway, so the reference would get lost anyway. Thus, the only way to do it is using a Get() and Set() parameters so that it returns or sets the int value within my CIntParameter.

That is, for one, a pain in the ass to program with (not being able to use "="), and for two, error-prone, because I could be misusing the = operator at some point (with another CIntParameter) and it would assign the reference of the other, so the original parameter reference would get lost.


2) Go unsafe and use pointers for everything... but I'd be using Unamanged C++ if I was to do that, right?

So, any of you know of any different approach to this problem, or can think of a better solution? (or been there?)

In conclusion, all of these Value-Types and Reference-Types deriving from the same System.Object is stupid IMHO, but the fact that you can't overload operator =, or you can't just assign a reference which the system could be tracing for value-types (it's tracing it with the garbage collector whenever you change a reference, so that it knows if the object is referenced by any other thing, and if it's not, dispose it, it could be easy for the CLR to reassign a reference, maybe using another different keyword). Or you could just have CLR-optimized reference-types for basic values (ref int, ref double, or something like that).

I hope I'm not being wrong with all of this, if I am, please someone explain it to me, and tell me how to handle this case. Hope I've explained myself clear.

Thank you very much for the long reading, and sorry for the HTML, those of you who can't see it. I can reformat a non-HTML post if you want me to (and if this doesn't see correctly with non-HTML viewers)

Javier Campos
Virtual Media Systems, S.L.
 
J

Javier Campos

[Continues...]

As for 1) of the approaches (wrapping up the value-types in my own class)
there are even more problems.

I can't directly use any operators other than comparison on the
CIntParameter (or however you want to call the class), because, unlike in
C++ (where you can overload non-static operators), all operator overloading
in C# must be static, so I have to make a "new" for each of them.

Example:

C++ Non Static Operator
-----------------------
/*
This allows you to do:

CVector a(1,1,1);
CVector b(2,2,2);
CVector *pv = &a;

a = a - b;


At no point in the "a = a - b", memory is allocated, as you can work
directly on the member variables of the instance [because the operator is
not static]. At the end of the operation, pv still points at the same
location "a", and it's members (pv->x, pv->y and pv->z) point to the correct
result of the operation "a = a - b", thus, all pv->x, pv->y and pv->z
value's will be -1, which is correct.)

*/

CVector &CVector::blush:perator -= (CVector &v)
{
x-=v.x;
y-=v.y;
z-=v.z;
return *this;
}

--------------------------------------------------------------
Best C# Approach to an operator '-' overloading for Substracting two Vectors
--------------------------------------------------------------
/*
Now I can do this:

CVector a = new CVector(1,1,1);
CVector b = new CVector(2,2,2);
CVector pv = a; // It's referenced, as CVector is a reference-type class

a = a - b;

At the end of "a = a - b", 'a' is a newly created vector, which doesn't
have the same memory address as the older 'a' we created, thus, 'pv' now
still points to a vector (for which we have no variable other than 'pv')
containing (1,1,1), whereas 'a' now contains (-1,-1,-1).

For what I've learnt of C# (I'm still a newbie though), there is no way I
could make such an operation, and keep 'pv' pointing to 'a' after using any
operator.

*/
public static CVector operator - (CVector v, CVector v2)
{
return new CVector(v.x-v2.x, v.y-v2.y, v.z-v2.z);
}


Just one more inconvenience of method 1 that I wanted to share with you (and
see if anyone has a different approach)

Thank you for reading,

Javier Campos
Virtual Media Systems, S.L.
 
J

Javier Campos

Juan Gabriel Del Cid said:
What's wrong with:

public static CVector operator - (CVector v, CVector v2) {
v.x = v.x - v2.x;
v.y = v.y - v2.y;
v.z = v.z - v2.z;

return v;
}

Yeah, that happens to me for being so quick when writing, I'm sorry, you are
right.
This is the same as your C++ implementation. I don't think the minus
operator should modify any of it's operands, though. This is a terible
design.

-JG

Yes, actually, my problem with this (I made up the CVector thing) happened
when trying to make the wrapper, and I started using explicit operators, so
I countinued doing so till I realized it was completely wrong (as I had to
use new to create new instances)

I'm sorry for my mistake, my bad... I got too excited talking about the
problem :)

Thank you,

Javier Campos
Virtual Media Systems, S.L.
 
J

Javier Campos

Nope, you got it all wrong. In line 2, the compiler intenally allocated
memory for the boxed integer and copied it's value there. Like this:

Stack Heap
+-------+ +------+
i | 5 | | ... |
+-------+ +------+
o | ref --+--------> | 5 | // this is the boxed integer
+-------+ +------+

That makes more sense, still doesn't help my problem at all :/
And yes, you're right, the ony way to reference to a value type is through
(unsafe) pointers.

Yes, this is one of the problems I'm facing with this. Having to use
pointers for all the possible scripted classes kinda makes me want to just
wrap the existing C++ engine, instead of porting it. Makes no sense at all
to port it once you have to use unsafe/unmanaged code.

I guess having a way to track "pointers" would not be that hard to make;
taking the garbage collector is running and actually checking for
references, it shouldn't make such a different in performance anyway, but I
might be all wrong with this (if I am, someone correct me).

They made a reference-type for a valued-type also (strings)... not that it
works the same, but if they can make an exception in the runtime (so it
optimizes better), I'm pretty sure it wouldn't be that hard to implement
some more, for other value-types like int, double, or whatnot... and I would
like to see those included in future releases. Having to wrap it is already
bad, but not having it in the standard framework (taking the same system is
limiting you to do some pretty basic stuff -like this-) is even worse, in my
oppinion.

I think I'll just take the wrapping approach and see how it goes for some
time... if I find that's too much of a pain in the ass, I'll just do that
part in C++ and keep doing the rest of the thing in C#. Still, I'd love to
hear oppinions from you all about this, and what did you do, if you ever got
into a situation like mine.

Thank you for pointing out my mistakes, Juan :)

Javier Campos
Virtual Media Systems, S.L.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top