C Sharp

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