Section 13.4. The is and as Operators


13.4. The is and as Operators

There may be times, however, in which you do not know at compile time whether or not an object supports a particular interface. For instance, given a List 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.

Any time you see casting, you must question the design of the program. It is common for casting to be the result of poor or lazy design. That said, there are times that casting is unavoidable, especially when dealing with nongeneric collections that you did not create.


You could cast each member blindly to ICompressible , and then catch the exception that will be thrown for those that are not ICompressible , but this is ugly, and there are two better ways to do so: the is and the as operators .

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 ( myObject is ICompressible ) 

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. [*]

[*] Historical footnote: "It depends on what the meaning of the word 'is' is. If theif heif 'is' means is and never has been, that is notthat is one thing."But not in C#.

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 ) 

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 and then, if you want to access either ICompressible or the ID , you'll need to test the Note to see if 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
 using System; namespace InterfaceDemo {    interface IStorable    {       void Read(  );       void Write( object obj );       int Status { get; set; }    }    interface ICompressible    {       void Compress(  );       void Decompress(  );    }    public class Note : IStorable    {       private int status = 0; // IStorable       private string myString;       public Note( string theString )       {          myString = theString;       }       public override string ToString(  )       {          return myString;       }       #region IStorable       public void Read(  )       {          Console.WriteLine(          "Implementing the Read Method for IStorable" );       }       public void Write( object o )       {          Console.WriteLine(          "Implementing the Write Method for IStorable" );       }       public int Status       {          get { return status; }          set { status = value; }       }       #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( "Compressing..." );       }       public void Decompress(  )       {          Console.WriteLine( "Decompressing..." );       }       #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( "\nTesting {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 );                // old fashioned cast!                Console.WriteLine( "My documentID is {0}",                  ( ( Document ) theNote ).ID );             }          }          foreach ( Note theNote in myNoteArray )          {             Console.WriteLine( "\nTesting {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 }             // end Namespace InterfaceDemo 

The output looks like this:

 Testing String 0 with IS     Implementing the Read Method for IStorable     Compressing...     my documentID is 50     My documentID is 50     Testing String 1 with IS     Implementing the Read Method for IStorable     This storable object is not compressible.     Testing String 2 with IS     Implementing the Read Method for IStorable     Compressing...     my documentID is 70     My documentID is 70     Testing String 0 with AS     Compressing...     My documentID is 50     Testing String 1 with AS     This storable object is not compressible.     Not a document.     Testing String 2 with AS     Compressing...     My documentID is 70 

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

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

Within the Run( ) method of the Tester class, we create an array of Note objects, and we add to that array two Document and one Note instances (using the expedient that each time through the for loop, we check whether the counter variable i is even, and if so, we create a Document ; otherwise , we create a Note ).

We then iterate through the array, extract each Note in turn , and use the is operator to test first if the Note can safely be assigned to an ICompressible reference and then to check if the Note can safely be cast to a Document . In the case shown, these tests amount to the same thing, but you can imagine that we could have a collection with many types derived from Note , some of which implement ICompressible and some of which do not.

We have a choice as to how we cast to a document. The old-fashioned way is to use the C-style cast:

 myDoc = (Document) theNote; 

The preferred way is to use the as operator:

 myDoc = theNote as Document; 

The advantage of the latter is that it will return null (rather than throwing an exception) if the cast fails, and it may be a good idea to get in the habit of using this new form of casting.

In any case, you can use the interim variable:

 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:

 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. (If you bother to look at the actual IL code, you'll see that the second foreach loop actually generates less code, and thus is slightly more efficient.)



Learning C# 2005
Learning C# 2005: Get Started with C# 2.0 and .NET Programming (2nd Edition)
ISBN: 0596102097
EAN: 2147483647
Year: 2004
Pages: 250

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net