Querying for Implementation by Using as
If you look closely at the MSIL code generated from the preceding IsOperator2App example-that MSIL code follows this paragraph-you'll notice one problem with the is operator. You can see that isinst is called right after the object is allocated and the stack is set up. The isinst opcode is generated by the compiler for the C# is operator. This opcode tests to see if the object is an instance of a class or interface. Notice that just a few lines later-assuming the conditional test passes-the compiler has generated the castclass IL opcode. The castclass opcode does its own verification and, while this opcode works slightly differently than isinst, the result is that the generated IL is doing inefficient work by checking the validity of this cast twice.
.method public hidebysig static void Main() il managed
{
.entrypoint
// Code size 72 (0x48)
.maxstack 4
.locals (class MyControl V_0,
class ISerializable V_1,
bool V_2)
IL_0000: newobj instance void MyControl::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: isinst ISerializable
IL_000c: brfalse.s IL_003d
IL_000e: ldloc.0
IL_000f: castclass ISerializable
IL_0014: stloc.1
IL_0015: ldloc.1
IL_0016: callvirt instance bool ISerializable::Save()
IL_001b: stloc.2
IL_001c: ldstr "The saving of '{0}' was {1}successful"
IL_0021: ldloc.0
IL_0022: call instance class System.String FancyControl::get_data()
IL_0027: ldloc.2
IL_0028: brtrue.s IL_0031
IL_002a: ldstr "not "
IL_002f: br.s IL_0036
IL_0031: ldstr ""
IL_0036: call void [mscorlib]System.Console::WriteLine(class System.String,
class System.Object,
class System.Object)
IL_003b: br.s IL_0047
IL_003d: ldstr "The ISerializable interface is not implemented."
IL_0042: call void [mscorlib]System.Console::WriteLine(class System.String)
IL_0047: ret
} // end of method IsOperator2App::Main
We can make this verification process more efficient using the as operator. The as operator converts between compatible types and takes the following form, where expression is any reference type: -
object = expression as type -
You can think of the as operator as a combination of the is operator and, if the two types in question are compatible, a cast. An important difference between the as operator and the is operator is that the as operator sets the object equal to null instead of returning a Boolean value if expression and type are incompatible. Our example can now be rewritten in the following more efficient manner: -
using System;
public class FancyControl
{
protected string Data;
public string data
{
get
{
return this.Data;
}
set
{
this.Data = value;
}
}
}
interface ISerializable
{
bool Save();
}
interface IValidate
{
bool Validate();
}
class MyControl : FancyControl, IValidate
{
public MyControl()
{
data = "my grid data";
}
public bool Validate()
{
Console.WriteLine("Validating...{0}", data);
return true;
}
}
class AsOperatorApp
{
public static void Main()
{
MyControl myControl = new MyControl();
ISerializable ser = myControl as ISerializable;
if (null != ser)
{
bool success = ser.Save();
Console.WriteLine("The saving of '{0}' was {1}successful",
myControl.data,
(true == success ? "" : "not "));
}
else
{
Console.WriteLine("The ISerializable interface is not implemented.");
}
}
}
Now the verification to ensure a valid cast is done only once, which is obviously much more efficient. At this point, let's revisit the MSIL code to see the impact that using the as operator has had: -
.method public hidebysig static void Main() il managed
{
.entrypoint
// Code size 67 (0x43)
.maxstack 4
.locals (class MyControl V_0,
class ISerializable V_1,
bool V_2)
IL_0000: newobj instance void MyControl::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: isinst ISerializable
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: brfalse.s IL_0038
IL_0010: ldloc.1
IL_0011: callvirt instance bool ISerializable::Save()
IL_0016: stloc.2
IL_0017: ldstr "The saving of '{0}' was {1}successful"
IL_001c: ldloc.0
IL_001d: call instance class System.String FancyControl::get_data()
IL_0022: ldloc.2
IL_0023: brtrue.s IL_002c
IL_0025: ldstr "not "
IL_002a: br.s IL_0031
IL_002c: ldstr ""
IL_0031: call void [mscorlib]System.Console::WriteLine(class System.String,
class System.Object,
class System.Object)
IL_0036: br.s IL_0042
IL_0038: ldstr "The ISerializable interface is not implemented."
IL_003d: call void [mscorlib]System.Console::WriteLine(class System.String)
IL_0042: ret
} // end of method AsOperatorApp::Main