Valhalla Legends Forums Archive | .NET Platform | Type Conversions and Lossyness

There are a couple questions here.  In comments and after the code sample.

            class TS { public int lossyVal = 0; }
            TS ts = new TS();
            ts.lossyVal = 555;                // we will attempt to lose this data via conversions
            Object tt = new object();
            // implicit cast
            tt = ts;
            Console.WriteLine("ts is type {0}", ts.GetType().ToString());
            Console.WriteLine("tt is type {0}", tt.GetType().ToString());
            // at this point, both tt and ts are varibles of type TS, so  ...
            ts = tt;    // why does compile fail without explicit cast?
            // is it because compiler cannot figure out what happened on "tt = ts" line?
            // I can buy that, but what about ..
            // cast ts to object, attempt to lose the lossyVal
            tt = (object) ts;
            Console.WriteLine("ts is type {0}", ts.GetType().ToString());
            Console.WriteLine("tt is type {0}", tt.GetType().ToString());
            // why is tt not demoted to System.Object, and lose the extra data??
            // tt should be a type that doesnt have a member called lossyVal, but ..
            Console.WriteLine("guess what .. ({0})", ((TS)tt).lossyVal);

Why did lossyVal not get clobbered when converting to Object?
May 10, 2010, 5:03 AM
Casting along objects that have inheritance-supported casting is basically like calling dynamic_cast<target-type> in C++.  An object in memory only has one type identity - when you cast between TS and object, you're just telling the compiler what operations your variable reference supports.

Let me write equivalent code in C++ (traditional C++, not C++/CLI); assume there are similar library methods and classes.
class TS : public Object
   int lossyVal = 0;

void main()
   TS* ts = new TS;
   ts.lossyVal = 555;

   Object* tt = new Object;
   tt = ts; /* note that with this assignment, the memory
pointed to by tt goes out of scope and is leaked, and tt
and ts both point to the same memory. */
   Console::WriteLine("ts is type {0}", ts->GetType()->ToString());
   Console::WriteLine("tt is type {0}", tt->GetType()->ToString());
   /* The lines above should produce the same output you saw */
   ts = dynamic_cast<TS*>(tt); /* Note that dynamic_cast
is required here because the compiler needs to adjust the
vtables.  That's why the C# compiler fails, too - it needs
to ensure type safety. */

   tt = ts; /* C++ and C# both support this assignment
without a cast.  tt still points to the same memory and
object identity pointed to by ts - the compiler just sees it
as an Object* */
   Console::WriteLine("guess what .. ({0})", (dynamic_cast<TS*>(tt))->lossyVal);
C# isn't a dynamic or scripting language - you can't change the type identity of an object once you create it in memory.  You only converted it to Object as far as the compiler sees, because you have two local references (analogous to C++ pointers) with different types.

There is one caveat - like C++, C# allows you to overload casting operators.  It defines two kinds of casts, implicit and explicit, which vary based on whether you need to specify the cast (an example of an implicit cast is in the LINQ-to-XML library, where all references to names use the XName type, which has an implicit cast operator from string to XName).  You could "demote" the object like you outlined here, but you'd actually be creating a new object:

class TS
   public int lossyVal = 0;
   public static explicit operator object(TS source)
       return new object();
Because overloading of casting operators is not an obvious activity which can result in pretty grungy side-effects (mostly with regard to memory and fragmentation), it's not a common practice in .NET libraries.  Overloading operators (like + or whatnot) is much more common, and even that is pretty uncommon.
June 5, 2010, 8:36 AM