4.4. Explicit Interface Member Implementations

Given an entity of type object, how can we discover if it supports a particular interface? One way is simply to ask, using the is operator:

public static void iterate( object o )
{
      // true if o implements IEnumerable
      if ( o is IEnumerable )
      {
           IEnumerable ie = (IEnumerable) o;
           IEnumerator iter = ie.GetEnumerator();

           while ( iter.MoveNext() )
                   Console.WriteLine( "{0} ",
                           iter.Current.ToString() );
      }
}

iterate() is an example of a generic function in C#. We have no idea as to the actual type that o refers to. We ask if it supports the IEnumerable interface. If it does not, that's an end to it. If it does, we access it through an abstract IEnumerator object, moving through the elements in turn. We have no idea of either the enumerator type or the type of collection element. This function can be invoked with any program type as its argument.

Generic programming provides an almost magically flexible implementation. We can invoke iterate() with an object of our Fibonacci class:

Fibonacci fib  = new Fibonacci();
// ...
iterate( fib );

or with an instance of an ArrayList, or a built-in array, and so on. In each case, everything works fine.

However, if we wish to iterate across our Fibonacci class object directly using an NSEnumerator object, the generic support begins to trip us up. We have to treat the return values generically, even though we know the actual type being returned—for example:

NSEnumerator nse = (NSEnumerator) fib.GetEnumerator();
while ( nse.MoveNext() )
{
   decimal el = (decimal) nse.Current; // downcast
   // ...
}

There are two ways to provide multiple instances of an interface member—one to be used when we are programming generically through the interface, and one to be used when we are programming an explicit instance of the interface, such as NSEnumerator. We do this through explicit interface member implementations—for example:

class NSEnumerator : IEnumerator
{
    private void checkIntegrity(){
      if ( m_curr == -1 || m_curr >= m_count )
           throw new InvalidOperationException( ToString() );
    }
    // invoked through an NSEnumerator object
    public decimal Current
         { get{ checkIntegrity(); return m_elems[ m_curr-1 ]; }}

    // the explicit interface member,
    // invoked only through a generic IEnumerator object
    object IEnumerator.Current
         { get{ checkIntegrity(); return m_elems[ m_curr-1 ]; }}

    // ...
}

We identify an explicit interface member implementation by prefixing the member name with the name of the interface in which the member is declared, followed by the scope operator (.):

object IEnumerator.Current { ... } // explicit member
public decimal Current     { ... }

The access level of an explicit member is implicitly public. An explicit access level, even that of public, is not allowed. If the interface that is used to identify an explicit member does not contain the member, an error results.

The explicit interface instance is invoked whenever Current is accessed through an IEnumerator object, such as in iterate():

NSEnumerator nse = (NSEnumerator) fib.GetEnumerator();

// downcast from object is no longer necessary
decimal el = nse.Current;

I(If we wish to eliminate the downcast of GetEnumerator(), we provide an explicit instance returning an IEnumerator and a nonexplicit instance returning an NSEnumerator.)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.181