The is and as Operators

Sometimes, however, you may not know at compile time whether an object supports a particular interface. For instance, if you have an array of IStorable objects, you might not know whether any given object in the collection also implements ICompressible (some do, some do not). Let’s set aside the question of whether this is a good design, and move on to how we solve the problem.

Warning

Anytime you see casting, you can question the design of the program. It is common for casting to be the result of poor or lazy design. That being said, sometimes casting is unavoidable, especially when dealing with collections that you did not create. This is one of those situations where experience over time will help you tell good designs from bad.

You could try casting each member blindly to ICompressible. If the object in question doesn’t implement ICompressible, an error will be raised. You could then handle that error, using techniques we’ll explain in Chapter 16. That’s a sloppy and ineffective way to do it, though. The is and as operators provide a much better way.

The is operator lets you query whether an object implements an interface (or derives from a base class). The form of the is operator is:

if ( expression is type )

The is operator evaluates true if the expression (which must be a reference type, such as an instance of a class) can be safely cast to type without throwing an exception.

The as operator is similar to is, but it goes a step further. The as operator tries to cast the object to the type, and if an exception would be thrown, it instead returns null:

ICompressible myCompressible = myObject as ICompressible
if ( myCompressible != null )

Warning

The is operator is slightly less efficient than using as, so the as operator is slightly preferred over the is operator, except when you want to do the test but not actually do the cast (a rare situation).

Example 13-3 illustrates the use of both the is and the as operators by creating two classes. The Note class implements IStorable. The Document class derives from Note (and thus inherits the implementation of IStorable) and adds a property (ID) along with an implementation of ICompressible.

In this example, you’ll create an array of Note objects (which could be either Notes or Documents) and then, if you want to access either ICompressible or the ID, you’ll need to test the Note to see whether it is of the correct type. Both the is and the as operators are demonstrated. The entire program is documented fully immediately after the source code.

Example 13-3. The is and as operators allow you to determine whether an object can be cast to an interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Example_13_3_ _ _ _is_and_as
{
    interface IStorable
    {
        void Read( );
        void Write(object obj);
        int Status { get; set; }
    }

    interface ICompressible
    {
        void Compress( );
        void Decompress( );
    }

    public class Note : IStorable
    {
        private string myString;

        public Note(string theString)
        {
            myString = theString;
        }

        public override string ToString( )
        {
            return myString;
        }

        #region IStorable

        public void Read( )
        {
            Console.WriteLine("Executing Note's Read Method
                               for IStorable");
        }

        public void Write(object o)
        {
            Console.WriteLine("Executing Note's Write Method
                               for IStorable");
        }

        public int Status { get; set; }

        #endregion // IStorable

    }

    public class Document : Note, ICompressible
    {

        private int documentID;
        public int ID
        {
            get { return this.documentID; }

        }

        public Document(string docString, int documentID)
                        : 
base(docString)
        {
            this.documentID = documentID;
        }

        #region ICompressible

        public void Compress( )
        {
            Console.WriteLine("Executing Document's Compress Method
                               for ICompressible");
        }
        public void Decompress( )
        {
            Console.WriteLine("Executing Document's Decompress Method
                               for ICompressible");
        }
        #endregion  // ICompressible

    }  // end Document class

    class Tester
    {
        public void Run( )
        {
            string testString = "String ";
            Note[] myNoteArray = new Note[3];

            for (int i = 0; i < 3; i++)
            {
                string docText = testString + i.ToString( );
                if (i % 2 == 0)
                {
                    Document myDocument = new Document(
                                    docText, (i + 5) * 10);
                    myNoteArray[i] = myDocument;
                }
                else
                {
                    Note myNote = new Note(docText);
                    myNoteArray[i] = myNote;
                }
            }

            foreach (Note theNote in myNoteArray)
            {
                Console.WriteLine("
Testing {0} with IS", theNote);

                theNote.Read( );     // all notes can do this
                if (theNote is ICompressible)
                {
                    ICompressible myCompressible =
                                theNote as ICompressible;
                    myCompressible.Compress( );
                }
                else
                {
                    Console.WriteLine("This storable object is
                                       not compressible.");
                }

                if (theNote is Document)
                {
                    Document myDoc = theNote as Document;

                    // clean cast
                    myDoc = theNote as Document;
                    Console.WriteLine("my documentID is {0}", myDoc.ID);
                }
            }

            foreach (Note theNote in myNoteArray)
            {
                Console.WriteLine("
Testing {0} with AS", theNote);
                ICompressible myCompressible = theNote as ICompressible;
                if (myCompressible != null)
                {
                    myCompressible.Compress( );
                }
                else
                {
                    Console.WriteLine("This storable object is
                                         not compressible.");
                }    // end else

                Document theDoc = theNote as Document;
                if (theDoc != null)
                {
                    Console.WriteLine("My documentID is {0}",
                       ((Document)theNote).ID);
                }
                else
                {
                    Console.WriteLine("Not a document.");
                }
            }
        }

        static void Main( )
        {
            Tester t = new Tester( );
            t.Run( );
        }
    }          // end class Tester
}

The output looks like this:

Testing String 0 with IS
Executing Note's Read Method for IStorable
Executing Document's Compress Method for ICompressible
my documentID is 50

Testing String 1 with IS
Executing Note's Read Method for IStorable
This storable object is not compressible.

Testing String 2 with IS
Executing Note's Read Method for IStorable
Executing Document's Compress Method for ICompressible
my documentID is 70

Testing String 0 with AS
Executing Document's Compress Method for ICompressible
My documentID is 50

Testing String 1 with AS
This storable object is not compressible.
Not a document.

Testing String 2 with AS
Executing Document's Compress Method for ICompressible
My documentID is 70

The best way to understand this program is to take it apart piece by piece.

Within the namespace, you declare two interfaces, IStorable and ICompressible, and then two classes: Note, which implements IStorable; and Document, which derives from Note (and thus inherits the implementation of IStorable) and which also implements ICompressible. Finally, you add the class Tester to test the program.

Within the Run( ) method of the Tester class, you create an array of Note objects, and you add to that array two Document instances and one Note instance. You use the counter i of the for loop as a control—if i is even, you create a Document object; if it’s odd, you create a Note.

You then iterate through the array, extracting each Note in turn, and use the is operator to test first whether the Note can safely be assigned to an ICompressible reference:

if (theNote is ICompressible)
{
    ICompressible myCompressible = theNote as ICompressible;
    myCompressible.Compress( );
}
else
{
    Console.WriteLine("This storable object is not compressible.");
}

If it can, you cast theNote to ICompressible, and call the Compress( ) method.

Then you check whether the Note can safely be cast to a Document:

if (theNote is Document)
{
    Document myDoc = theNote as Document;

    // clean cast
    myDoc = theNote as Document;
    Console.WriteLine("my documentID is {0}", myDoc.ID);
}

In the case shown, these tests amount to the same thing, but you can imagine that you could have a collection with many types derived from Note, some of which implement ICompressible and some of which do not.

You can use the interim variable as we’ve done here:

myDoc = theNote as Document;
Console.WriteLine( "my documentID is {0}", myDoc.ID );

Or, you can cast and access the property all in one ugly but effective line, as you do in the second loop:

Console.WriteLine( "My documentID is {0}",
   ( ( Document ) theNote ).ID );

The extra parentheses are required to ensure that the cast is done before the attempt at accessing the property.

The second foreach loop uses the as operator to accomplish the same work, and the results are identical. (The second foreach loop actually generates less intermediate language code, and thus is slightly more efficient.)

..................Content has been hidden....................

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