Is or As that is the question ... - A world apart from the everday ...

A world apart from the everday ...

Assert.IsTrue(Entries.Count == 0);

Is or As that is the question ...

I got asked the questions recently;

"What is the difference between the Is and the As operators in C#, when would you use each? Are they interchangeable? What are the performance implications of each?"

I had an idea but I didn’t know the exact answer to all of the above, and neither did some mates I ping'd so I thought I'd look into this in a bit more detail. The purpose of this post then is partly for my reference and partly to educate them (and those out there who are in their shoes too but embarrassed to admit it.

I will be using the following two interfaces in my examples, trivial yes, but they should suffice in helping me illustrate the differences and the usage of each operator.

interface IEncryptable
{
    void Encrypt();
    void Decrypt();
}
interface ICompressible
{
    void Compress();
    void Decompress();
}

Now that we have our two interfaces, let's create some classes to implement these interfaces. For the sake of keeping this post as brief as possible I will not flesh out each method. If you want the full source code that badly just ask and I'll post a link to the solution.

 

public class Document: IEncryptable
{
    #region IEncryptable Members

    public void Encrypt()
    {
    }

    public void Decrypt()
    {
    }

    #endregion
}

//Create a new specialized type of Document that is compressible
public class CompressibleDocument : Document, ICompressible
{
    #region ICompressible Members

    public void Compress()
    {
    }

    public void Decompress()
    {
    }

    #endregion
}

So, we have some demo classes (really intense and complicate, I know ....) now let's stop the waffle and finally get to the meat of this post.

I guess the question is....

How do you know what interface(s) a class implements at runtime? Well there are two methods.... yip, you guessed them... using "is" and "as".

But why would the good folk at Redmond provide two operators to do the same thing you ask, well they didn't. There is a subtle, but important, difference between the two and understanding this difference can lead to better performance; and we all want to perform better, don't we?

"Is":

Consider the following code ~

 

static void Main(string[] args)
{
    //A collection of different types of documents
    Document[] docs = new Document[2];

    //First element is a regular Document
    docs[0] = new Document();

    //Second element is a special document, one that be compressed
    docs[1] = new CompressibleDocument();

    //Let's loop through our collection of documents
    //Remember at this point we don't know whether we've got a Document
    //that can be compressed or not, all we know is that we 
    //have a document of some form.
    foreach (Document doc in docs)
    {
        //Only compressible documents will pass here
        if (doc is ICompressible)
        {
            ICompressible compressibleDoc = (ICompressible)doc;
            compressibleDoc.Compress();
        }
    }
}

Simply put this friendly chap, the "Is" operator offers a nice way to ask a class are you something else, or, can you do something because you implement a particular interface or derive from a particular class.

Let's dig into some MSIL code quickly to have a look at what is happening when we do the "Is" checks above.

 

IL_0035: isinst Is_Vs_As.ICompressible
IL_003a: ldnull
IL_003b: cgt.un
IL_003d: ldc.i4.0
IL_003e: ceq
IL_0040: stloc.s CS$4$0001
IL_0042: ldloc.s CS$4$0001
IL_0044: brtrue.s IL_0056
IL_0046: nop
IL_0047: ldloc.0
IL_0048: castclass Is_Vs_As.ICompressible
IL_004d: stloc.2
IL_004e: ldloc.2


IL_004f: callvirt instance void Is_Vs_As.ICompressible::Compress()

 

 

 

 

 

 

 

 

 

 

 

 

 

I have marked the important lines for this example in bold.

The keyword, on line 35, isinst is the MSIL code translation of the “Is” operator. The cast is done and the test is done on line 44 with the brtrue.s IL_0056 instruction. If the test passes the code will continue down to the next bold line, 48, where castclass is called.

As you will see when we get to the “As” operator in a bit the castclass operation also performs a check on the type before a cast is made, if the type is not valid null is returned from the cast operation. So we're actually checking the type twice.

In effect the code (doc is ICompressible) can be translated into ( (doc is as ICompressible)!= null) )

Now let's move on

“As":

Consider the following code ~

 

//Let's loop through our collection of documents
//Remember at this point we don't know whether we've got a Document 
//that can be compressed or not, all we know is that we have a 
//document of some form.
foreach (Document doc in docs)
{
    //Only compressible documents will pass here
    ICompressible compressibleDoc = doc as ICompressible;
    if (compressibleDoc != null)
    {
        compressibleDoc.Compress();
    }
}

Here we are using the As operator to do a direct cast to the ICompressible interface. If the type of the class we're trying to cast is not valid for this cast the As operation will return a null reference. So all we need to do is check for the null reference before attempting an operation that is specific to the ICompressible interface.

Let's have a look at what is happening this time around in the MSIL code

IL_0035: isinst Is_Vs_As.ICompressible
IL_003a: stloc.2
IL_003b: ldloc.2
IL_003c: ldnull
IL_003d: ceq
IL_003f: stloc.s CS$4$0001
IL_0041: ldloc.s CS$4$0001
IL_0043: brtrue.s IL_004e
IL_0045: nop
IL_0046: ldloc.2
IL_0047: callvirt instance void Is_Vs_As.ICompressible::Compress()

 

 

 

 

 

 

 

 

 

 

Again I have marked the important bits in bold. You will notice when comparing to the MSIL code from the Is operator that there is a distinct lack of a castclass instruction.

Clearly more efficient than the Is operator from above.

As a side note,  ICompressible compressibleDoc = (ICompressible) doc;

Will throw a InvalidCastException if the type of the class is invalid for the specific cast as opposed to the As operator that will simply return a null for invalid casts, making the As operator really powerful in situations like this .... much easier than trying to handle the possible InvalidCastException.

 

 

 

 

 

So if the As operator is more efficient than the Is operator as we've shown above then why would they include both?

Well simply put if your design is to check the type and do a cast immediately, as done here and most often the case, then the As operator is more efficient.

However, if you simply want to test the type without casting it at all, as shown below, then the Is operator would be a better choice....

 

ArrayList someList = new ArrayList();
foreach (Document doc in docs)
{
    //Only compressible documents will pass here
    ICompressible compressibleDoc = doc as ICompressible;
    if (compressibleDoc != null)
    {
        someList.Add(doc);
    }
}

 

IL_0026: isinst Is_Vs_As.ICompressible
IL_002b: ldnull
IL_002c: cgt.un
IL_002e: ldc.i4.0
IL_002f: ceq
IL_0031: stloc.3
IL_0032: ldloc.3
IL_0033: brtrue.s IL_003f
IL_0035: nop
IL_0036: ldloc.0
IL_0037: ldloc.1
IL_0038: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)

 

 

 

 

 

 

 

 

 

 

 

 

Ok so we know which is now more efficient and when to use which.... But how more effecient is the As operator against the Is when doing a check followed immediately by a cast?

Well let's check.

I used the classes above and created a few more, up to 100,000,000 [yes that is 100 MILLION], documents instead of 2. I then ran through each set of worker functions, one using the Is operator and the other using the As.

I added some timing to each and ran the tests a few times to try average out the results; below are my results. (Results will obviously be different depending on your machine etc.)

Documents In Collection DoIs time in Ticks DoAs time in Ticks % Diff
1,000 859 272 68.34
10,000 844 691 18.13
100,000 5,283 5,165 2.23
1,000,000 50,839 47,733 6.11
10,000,000 508,821 476,447 6.36
100,000,000 5,102,190 4,761,784 6.67

 

 

 

 

 

 

 

What I find very interesting is that in relation to the Is operator the As is quicker the smaller the collection; Anybody know why?

As you can see there is a difference but I mean really ... we’re talking ticks here;

So if 1 tick = 100 nanoseconds And 1 millisecond = 1,000,000 nanoseconds then are 10,000 ticks in a millisecond.

Therefore even with 100 MILLION ( *said in best Dr Evil voice* ) we’re looking at 0.51 seconds vs 0.47 seconds and a “massive” saving of 0.034 seconds!

So should we really split hairs over this?

I feel a little deflated now I must admit.

Comments

Craig Nicholson said:

The reason you use "as" over "is" in this situation is because its the "right thing to do". :) And FxCop will also tell you to do it, so therefore you should. ;)

# August 15, 2007 11:34 PM

Ryan CrawCour said:

LOL, funny guy. but i guess you are correct. it is the "right" thing to do especially if you are massively concerned about every millisecond.

# August 16, 2007 6:13 AM

Ryan CrawCour said:

Who can explain the phenomenon that occurs showing that the As operator is more efficient with smaller collections relatively speaking than then Is operator?

Is the extra overhead coming purely from .NET having to try handle that size of collection.

From 1,000,000 records onwards the advantage of the As operator drops drastically down to only 6%. Even for 10,000 items in the collection it is only 18%.

# August 16, 2007 6:14 AM

Ernst Kuschke said:

There's quite a big difference between the two - "as" converts a type from one to another, where "is" returns a boolean.

According to the C# spec:

"The is operator is used to check whether the run-time type of an object is compatible with a given type."

"The as operator is used to perform conversions between compatible types."

# August 16, 2007 11:07 AM

Ryan CrawCour said:

Ernst; yeah i hear you on the differences that is the reason ultimately behind a post like this.

However i feel the documentation for the "is" operator is slightly over simplified in that it doesn't just check for compatibility, but in fact does the conversion, and then checks whether or not the result is null.

As shown in the post the "is" operator (IMHO) can be thought of as the following;

( (doc as ICompressible)!= null) )

# August 16, 2007 11:14 AM

Shaun said:

Great post. One of the best I've read for a really long time. ScottGu would be proud.

# August 16, 2007 8:25 PM

Ernst Kuschke said:

Hey Ryan,

Half of my comment was lost :P

Let's try again! I do agree: it's a very cool post, but I thought it'd be cool, maybe as a future post, to show the performance differences between using "as" and doing a conventional cast.

They are quite different, too ;o)

# August 17, 2007 11:32 AM

Ernst Kuschke said:

Yo Ryan,

in your blogpost, you said:

"In effect the code (doc is ICompressible) can be translated into ( (doc is ICompressible)!= null) )"

I know you meant "(doc is ICompressible) can be translated into ((doc as ICompressible)!= null)" now :-)

This also highlights the major difference between "as" and a cast: "as" is safer.

"as" will return null if the object can not be cast, where

(ICompressible)NotACompressibleInstance

will throw an exception.

# August 17, 2007 11:38 AM

Ryan CrawCour said:

Ernst,

Consider it done. as soon as i find the time to work through it i shall whack this together.

# August 17, 2007 11:40 AM

Ryan CrawCour said:

Hmmmm, My bad .... :$

I only see that now. Typos! jeesh you got to hate them. Will have to go update that.

# August 17, 2007 11:50 AM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: