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.