Casting

Casting

Casting a derived object to a base type is always safe. As shown in the previous section, this is the common cast for polymorphism and is legitimate in all circumstances. Derived types extend base types. Because derived types encompass everything about the base type, a derived-to-base-type cast is guaranteed to be safe. You can even cast a derived instance to an abstract base class. This was demonstrated earlier. Casting from a derived object to a base reference provides a base view of the instance. You are limited to members of the base type. The refinements of the derived type are not visible through a base reference to a derived instance, although the derived class implementation of an abstract base class method will be visible. After the cast, the base reference is an alias to the original derived object.

Casting a value type to a base interface has different semantics. The result of casting a reference type to an interface is an alias. However, when casting a value type to an interface, a separate entity is created. Interfaces are reference types. Boxing always occurs when casting a value type to a reference type. This includes casting a value type to an interface. Boxing creates a copy of the value type, which is placed on the managed heap. The original and copy are unrelated. Changes to one will not affect the other.

In the following code, the ZStruct structure implements the IAdd interface. Structures are value types. The IAdd.Increment method increments a counter. In Main, a ZStruct local variable is cast to an IAdd interface, which is legitimate. Boxing happens, and a copy of the ZStruct is placed on the managed heap. Changes to one will not affect the other. When the counts are displayed, the variables have different counts that confirm their separate identities.

using System;

namespace Donis.CSharpBook{

    public class Starter{
        public static void Main(){
            XStruct val=new XStruct();
            val.Increment();
            IAdd obj=val;
            val.Increment();
            Console.WriteLine("Val: {0}",
                val.Count);
            Console.WriteLine("Obj: {0}",
                obj.Count);
        }
    }

    public interface IAdd {
    void Increment();
        int Count {
            get;
        }
    }

    public struct XStruct: IAdd {

    public void Increment() {
            ++propCount;
        }

        private int propCount;
        public int Count {
            get {
                return propCount;

            }
        }
    }
}

A base class or interface can be used as a function parameter or return value. In this circumstance, you can substitute any related type for the function parameter or return value. There are three major reasons to use a base class or interface as a parameter or return value:

  • It generalizes a function call or return. The function parameter or return can be used with different types.

  • A specific parameter or return type may not be known at compile time. A base reference supports late binding, where the type is selected at run time.

  • Returning a base class or interface restricts access to an object. This is especially useful for class libraries that want to hide internal implementation.

The following code is an example of a class library—albeit a rather small library. The library contains a single class, which is the ZClass. It is marked as internal and visible solely within the class library. IExposed defines the public face of the ZClass type and clients of the library. LibraryClass.Method returns a ZClass instance through the IExposed public interface. This prevents clients from accessing the other methods of the ZClass. Those methods are internal and reserved for use in the class library.

using System;
namespace Donis.CSharpBook{

    public class LibraryClass{
        public IExposed GetSomething() {
            ZClass obj=new ZClass();
            // do something
            obj.InternalA();
            obj.InternalB();
            obj.MethodA();
            return obj;
        }
    }

    public interface IExposed {
        void MethodA();
        void MethodB();
    }

    internal class ZClass: IExposed {
        public void MethodA() {
        }

        public void MethodB() {
        }

        public void InternalA() {
        }

        public void InternalB() {
        }

    }
}

As shown several times in this chapter, casting a derived object to a base reference is safe. However, you cannot implicitly cast a base object to a derived reference. The derived type might have members not defined in the base type. Therefore, the derived reference might access members not available to the base object. For this reason, the cast is unsafe. An explicit cast can force the compiler to accept the unsafe cast of a base object to a derived reference. Because the cast remains unsafe, an exception is raised at run time at the cast. You have simply deferred the problem from compile time to run time.

This code raises an exception at run time because of an invalid cast:

using System;

namespace Donis.CSharpBook{

    public class Starter{
        public static void Main(){
            ZClass obj=new ZClass();

            // Fails at compile time
            // YClass alias=obj;

            // Fails at run time
            YClass alias=(YClass) obj;

            obj.MethodA();
            obj.MethodB();

        }
    }

    public class ZClass {
    public virtual void MethodA() {
        }
    public virtual void MethodB() {
        }
    }

    public class YClass: ZClass {
    public override void MethodA() {
        }
    }
}

Inheritance Operators

The is and as operators are convenient tools for testing the pedigree of an instance. These operators confirm the presence of a class or interface somewhere in the hierarchy of an instance. This confirmation is useful for promoting type-safe function calls. You can also develop algorithms that vary based on class types, for example, to display extra menu items for managers.

This is the syntax of the is operator:

  • bool result=expression is type;

The is operator returns true if the expression type and target type are related. If the expression type and target type are unrelated, false is returned.

The following code displays a menu. If employee is a manager, additional menu items are displayed. The is operator confirms that the employee is a manager.

using System;

namespace Donis.CSharpBook{

    public class Starter{
        public static void Main(){
             Manager person=new Manager("Accounting");
             Console.WriteLine("[Menu]\n");
             Console.WriteLine("Task 1");
             Console.WriteLine("Task 2");
             if(person is IManager) {
                 IManager mgr=person;
                 Console.WriteLine("\n[{0} Menu]\n",
                     mgr.Department);
                 Console.WriteLine("Task 3");
             }
         }
    }

    public interface IManager {
        string Department {
            get;
        }
    }

    public class Employee {
    }

    public class SalariedEmployee: Employee {
    }

    public class Manager: SalariedEmployee, IManager {

        public Manager(string dept) {
            propDepartment=dept;
        }

        private string propDepartment;
        public string Department {

            get {
                return propDepartment;
            }
        }
    }
}

This is the syntax of the as operator:

  • typeinstance=expression as type

The as operator evaluates an expression in the context of the target type. If the expression type and target type are related, an instance of the target type is returned. If the expression type and target type are unrelated, null is returned.

Here is the previous code rewritten with the as operator (this is a partial listing):

using System;

namespace Donis.CSharpBook{

    public class Starter{
        public static void Main(){
             Manager person=new Manager("Accounting");
             Console.WriteLine("[Menu]\n");
             Console.WriteLine("Task 1");
             Console.WriteLine("Task 2");
             IManager mgr=person as IManager;
             if(mgr != null) {
                 Console.WriteLine("\n[{0} Menu]\n",
                     mgr.Department);
                 Console.WriteLine("Task 3");
             }
         }
    }

// Partial listing