Even though unboxed value types don't have a type object pointer, you can still call virtual
methods (such as Equals, GetHashCode, or ToString) inherited or overridden by the type. The
reason is because the CLR can just call these methods nonvirtually and System.ValueType
overrides all of these virtual methods and expects the value in the this argument to refer to an
unboxed value type instance. Remember, a value type is implicitly sealed, and therefore, a
value type cannot be used as the base class of another type. This means that it's impossible for
a value type's virtual method to be overridden by a derived type. This also means that the CLR
can call a value type's virtual methods nonvirtually.
However, calling a nonvirtual inherited method (such as GetType or MemberwiseClone)
requires the value type to be boxed because these methods are defined by System.Object,
so the methods expect the this argument to be a pointer that refers to an object on the heap.
In addition, casting an unboxed instance of a value type to one of the type's interfaces requires
the instance to be boxed, because interface variables must always contain a reference to an
object on the heap. The following code
demonstrates:
Expand|Select|Wrap|Line Numbers
- using System;
- internal struct Point : IComparable {
- private Int32 m_x, m_y;
- // Constructor to easily initialize the fields
- public Point(Int32 x, Int32 y) {
- m_x = x;
- m_y = y;
- }
- // Override ToString method inherited from System.ValueType
- public override String ToString() {
- // Return the point as a string
- return String.Format("({0}, {1})", m_x, m_y);
- }
- // Implementation of type-safe CompareTo method
- public Int32 CompareTo(Point other) {
- // Use the Pythagorean Theorem to calculate
- // which point is farther from the origin (0, 0)
- return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)
- - Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y
- }
- // Implementation of IComparable's CompareTo method
- public Int32 CompareTo(Object o) {
- if (GetType() != o.GetType()) {
- throw new ArgumentException("o is not a Point");
- }
- // Call type-safe CompareTo method
- return CompareTo((Point) o);
- }
- }
- public static class Program {
- public static void Main() {
- // Create two Point instances on the stack.
- Point p1 = new Point(10, 10);
- Point p2 = new Point(20, 20);
- // p1 does NOT get boxed to call ToString (a virtual method).
- Console.WriteLine(p1.ToString()); // "(10, 10)"
- // p1 does NOT get boxed to call CompareTo.
- // p2 does NOT get boxed because CompareTo(Point) is called.
- Console.WriteLine(p1.CompareTo(p2)); // "-1"
- // p1 DOES get boxed, and the reference is placed in c.
- IComparable c = p1;
- Console.WriteLine(c.GetType()); // "Point"
- // p1 does NOT get boxed to call CompareTo.
- // Since CompareTo is not being passed a Point variable,
- // CompareTo(Object) is called which requires a reference to
- // a boxed Point.
- // c does NOT get boxed because it already refers to a boxed Point.
- Console.WriteLine(p1.CompareTo(c)); // "0"
- // c does NOT get boxed because it already refers to a boxed Point.
- // p2 does get boxed because CompareTo(Object) is called.
- Console.WriteLine(c.CompareTo(p2)); // "-1"
- // c is unboxed, and fields are copied into p2.
- p2 = (Point) c;
- // Proves that the fields got copied into p2.
- console.WriteLine(p2.ToString()); // "(10, 10)"
- }
This code demonstrates several scenarios related to boxing and unboxing:
• Calling ToString In the call to ToString, p1 doesn't have to be boxed. At first, you'd
think that p1 would have to be boxed because ToString is a virtual method that is inher-
ited from the base type, System.ValueType. Normally, to call a virtual method, the CLR
needs to determine the object's type in order to locate the type's method table. Since p1
is an unboxed value type, there's no type object pointer. However, the C# compiler sees
that Point overrides the ToString method, and it emits code that calls ToString directly
(nonvirtually) without having to do any boxing. The compiler knows that polymor-
phism can't come into play here since Point is a value type, and no type can derive from
it to provide another implementation of this virtual method.
• Calling GetType In the call to the nonvirtual GetType method, p1 does have to be
boxed. The reason is that the Point type inheritsGetType fromSystem.Object. So to call
GetType, the CLR must use a pointer to a type object, which can be obtained only by
boxing p1.
• Calling CompareTo (first time) In the first call to CompareTo, p1 doesn't have to be boxed
because Point implements the CompareTo method, and the compiler can just call it
directly. Note that aPoint variable (p2) is being passed toCompareTo, and therefore, the
compiler calls the overload of CompareTo that accepts a Point parameter. This means
that p2 will be passed by value to CompareTo and no boxing is necessary.
// p DOES get boxed to call GetType (a non-virtual method).
Console.WriteLine(p1.GetType()); // "Point"
// p1 does NOT get boxed to call CompareTo.
// p2 does NOT get boxed because CompareTo(Point) is called.
Console.WriteLine(p1.CompareTo(p2)); // "-1"
// p1 DOES get boxed, and the reference is placed in c.
IComparable c = p1;
Console.WriteLine(c.GetType()); // "Point"
// p1 does NOT get boxed to call CompareTo.
// Since CompareTo is not being passed a Point variable,
// CompareTo(Object) is called which requires a reference to
// a boxed Point.
// c does NOT get boxed because it already refers to a boxed Point.
Console.WriteLine(p1.CompareTo(c)); // "0"
// c does NOT get boxed because it already refers to a boxed Point.
// p2 does get boxed because CompareTo(Object) is called.
Console.WriteLine(c.CompareTo(p2)); // "-1"
// c is unboxed, and fields are copied into p2.
p2 = (Point) c;
// Proves that the fields got copied into p2.
console.WriteLine(p2.ToString()); // "(10, 10)"
}
}Chapter 5: Primitive, Reference, and Value Types 141
• Casting to IComparable When casting p1 to a variable (c) that is of an interface type,
p1 must be boxed because interfaces are reference types by definition. So p1 is boxed,
and the pointer to this boxed object is stored in the variable c. The following call to
GetType proves that c does refer to a boxed Point on the heap.
• Calling CompareTo (second time) In the second call to CompareTo, p1 doesn't have to be
boxed becausePoint implements theCompareTo method, and the compiler can just call
it directly. Note that an IComparable variable (c) is being passed-toCompareTo, and
therefore, the compiler calls the overload of CompareTo that accepts anObject para-
meter. This means that the argument passed must be a pointer that refers to an object on
the heap. Fortunately, c does refer to a boxed Point, and therefore, that memory address
in c can be passed to CompareTo, and no additional boxing is necessary.
• Calling CompareTo (third time) In the third call to CompareTo, c already refers to a boxed
Point object on the heap. Since c is of the IComparable interface type, you can call only
the interface's CompareTo method that requires an Object parameter. This means that
the argument passed must be a pointer that refers to an object on the heap. So p2 is
boxed, and the pointer to this boxed object is passed to CompareTo.
• Casting to Point When casting c to a Point, the object on the heap referred to by c is
unboxed, and its fields are copied from the heap to p2, an instance of the Point type
residing on the stack.
PLEASE HELP ME UNDERSTAND IT.
How can a virtual method be called non-virtually ?