Casting

Casting an instance of a derived class to a base type is always safe. As shown in the previous section, this is the cast for polymorphism and is legitimate in all circumstances. Derived types extend base types. Because derived types encompass everything about the base type, a cast that is derived to the base type is guaranteed to be safe. You can even cast a derived instance to an abstract base class. This has already been demonstrated more than once in this chapter. 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 extensions of the derived type are not visible through a base reference to a derived instance. After the cast to the base type, the base reference is an alias to the base portion of the derived object.

Casting a value type to a base interface has different semantics. When casting a value type to an interface, a separate entity is created. Interfaces are reference types. Boxing occurs any time a value type is cast to a reference type, including casting a value type to an interface. Boxing allocates memory and copies the value type to the managed heap. The original and the copy are unrelated. Changes to one will not affect the other.

In the following code, the ZStruct structure, which is a value type, implements the IAdd interface. The implementation of the Increment method increments an internal 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. The val variable is the instance on the stack, while obj references the copy placed on the managed heap. Changes to one will not affect the other. When the counts are displayed for the variables val and obj, they are different. This confirms 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;
            }
        }
    }
}

In the preceding code, XStruct.propCount is initialized implicitly. As mentioned in Chapter 2, fields in a struct are initialized to default values automatically.

A base class or interface can be used as a function parameter or return value. Then you can provide an instance of a derived type as the parameter or return value. This will provide polymorphic behavior in the called function or calling function, respectively. These are some 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 but related types.

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

  • Returning a base class or interface restricts access to an object. This is especially useful for class libraries that want to hide some portion of a public interface.

The following code is an example of a class library—albeit a rather small library. The library contains a single class, ZClass. It is marked as internal and is visible solely within the class library. IExposed defines the public face of the ZClass type to clients of the library. LibraryClass.GetSomething returns a ZClass instance through the IExposed public interface, which includes MethodA and MethodB. This prevents clients from accessing the other methods of 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 already in this chapter, casting a derived object to a base reference is safe. However, you cannot cast a base object implicitly to a derived reference. The derived type might have members that are not defined in the base type. Therefore, the derived reference could access members that are not available to the base object. For this reason, the cast is not type-safe. An explicit cast can force the compiler to accept the improper cast of a base object to a derived reference. Because this remains invalid, 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() {
        }
    }
}

Type Operators

The is and as type 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 provides run-time type information where decisions can be made at run time based on the type of an instance. For example, you might display extra menu items for employees who are managers.

Here is the syntax of the is operator, which is an expression that evaluates to a Boolean result:

  • instance is type;

The is operator returns true if the instance is related to the type. (Related means it is the same type or is derived from the type.) If the instance and type are unrelated, false is returned.

The following code displays a menu. If the 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]
");
             Console.WriteLine("Task 1");
             Console.WriteLine("Task 2");
             if (person is IManager) {
                 IManager mgr = person;
                 Console.WriteLine("
[{0} Menu]
",
                     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;
            }
        }
    }
}

Here is the syntax of the as operator:

  • instance1 = instance2 as type

The as operator casts instance2 to the related type. The result of the cast is placed in instance1. If instance2 is unrelated to type, instance1 is set to null.

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]
");
             Console.WriteLine("Task 1");
             Console.WriteLine("Task 2");
             IManager mgr = person as IManager;
             if (mgr != null) {
                 Console.WriteLine("
[{0} Menu]
",
                     mgr.Department);
                 Console.WriteLine("Task 3");
             }
         }
    }

// Partial listing
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.22.23