Chapter 5 - Working with Interfaces

The content and code of this article is referenced from book Pro C#5.0 and the .NET 4.5 Framework by Apress. The intention of the writing is to review the konwledge and gain better understanding of the .net framework. 

 

1. Understanding Interface Types

An interface is nothing more than a named set of abstract methods. Abstract methods are pure protocol in that they do not provide a default implementation. Said another way, an interface expresses a behavior that a given class or structure may choose to support. 

1.1 Interface vs Abstract base class

Recall that when a class is marked as abstract, it may define any number of abstract members to provide a polymorphic interface to all derived types. However, even when a class does define a set of abstract members, it is also free to define any number of constructors, field data, non abstract members (with implementation), and so on. Interfaces, on the other hand, contains only abstract member of definition. 

The major limitation is that only derived types support the members defined by the absract parent. However, in large system, it is very common to develop multiple class hierarchies that have no common parent beyond System.Object. After the interface has been defined, it can be implemented by any class or structure, in any hierarchy, within any namespace or any assembly. As you can see, the interfaces are highly polymorphic. 

1.2 Define custom interface

    public interface IPointy
    {
        //implicit public and abstract
        byte GetNumberOfPoints();
    }

Unlike a class, interfaces never specify a base class and the members of an interface never specify an access modifier (as all interface members are implicitly public and abstract)

Interfaces are pure protocol and, therefore, never define an implementation. 

    public interface IPointy
    {
        //implicit public and abstract
        byte GetNumberOfPoints(){return 8;} //error, cannot have implementation
        public int numOfPoints; //error, interfaces cannot have data fields
        public IPointy() {} //error, interface do not have constructor


    }

.Net interface types are able to define any number of property prototypes. 

    public interface IPointy
    {
        //implicit public and abstract
        byte GetNumberOfPoints(); 
        byte Points{ get; set;}
    }

Interface types are quite useless on their own, as they are nothing more than a named collection of abstract members. And you can't allocate interface types as you would a class or structure. 

1.3 Implementing an interface

When a class choose to extend its functionality by supporting interfaces, it does so using a comma-delimited list in the type definition. 

    public class Pencil : IPointy{

        public byte GetNumberOfPoints ()
        {
            return 8;
        }

        public byte Points {
            get {
                throw new NotImplementedException ();
            }
            set {
                throw new NotImplementedException ();
            }
        }
    }

 

1.4 Invoking interface members at the object level

The most straightforward way to interact with functionality supplied by a given interface is to invokde the members directly from the object level. 

        public static void Main (string[] args)
        {
            Hexagon hex = new Hexagon (); //hexagon has implemented IPointy
            Console.WriteLine ("Points: {0}", hex.Points);
        }

This approach workds fine in this case, given that you are well aware that the Hexagon type has implemented the interface, and , thererore, has a Points property. Other times, for example, suppose you have an array containing 50 shape-compatible types, only some of which support IPointy. You would receive an error, if you attempt to invoke the Points property on a type that has not implemented IPointy. 

One way to determine at runtime whether a type supports a specific interface is to make use of an explicit cast. If the type does not support the requested interface, you receive an InvalidCastException. 

        public static void Main (string[] args)
        {
            Circle c = new Circle ("Lisa");
            IPointy ipoint = null;
            try
            {
                ipoint = (IPointy)c;
            }
            catch(InvalidCastException e) {
                Console.WriteLine (e.Message);
            }
        }

1.5 The as keyword

You can determine whether a given type supports an interface by using the as keyword. 

        public static void Main (string[] args)
        {
            Hexagon hex = new Hexagon ("Peter");
            IPointy ipoint = hex as IPointy;
            if (ipoint != null)
                Console.WriteLine ("Points : {0}", ipoint.Points);
            else
                Console.WriteLine ("Not pointy..");
        }

1.6 The is keyword

        public static void Main (string[] args)
        {
            Hexagon hex = new Hexagon ("Peter");
            if (hex is IPointy) {
                Console.WriteLine ("Points : {0}", ((IPointy)hex).Points);
            }
        }

 

2. Interfaces as prameters and return type

Given that interfaces are valid .net types, you may construct methods that take interfaces as parameters. 

 

        //interface as parameter
        static void DrawIn3D(IDraw3D itf3d)
        {
            itf3d.Draw3D ();
        }

 

Interface can also be used as method return values. 

2.1 Array of interface types

Recall that the same interface can be implemented by numerous types, even if they are not within the same class hierarchy and do not have a common parent class beyond System.Object. 

        public static void Main (string[] args)
        {
            //this array can only contain types that implement the Ipointy interface
            IPointy[] pointyObjects = {new Hexagon(), new Knife(), new Fork(), new PitchForm() };


            foreach (IPointy i in pointyObjects) {
                Console.WriteLine ("Object has {0} points", i.Points);
            }

        }

Just to highlight the importance of the example, remember this: when you have an array of a given interface, the array can contain any class or structure which implements that interface. 

2.2 Explicit interface implementation

As we know, a single class or structure can implement any number of interfaces. There is always the possibility you might implement interfaces that contain identical members and, therefore, have a name class to contend with. 

    public interface IDrawToForm
    {
        void Draw();
    }

    public interface IDrawToMemory
    {
        void Draw();
    }

    public interface IDrawToPrinter
    {
        void Draw();
    }


Notice that each interface defines a method named Draw(), with the identical signature. 

    public class Octagon : IDrawToForm, IDrawToMemory, IDrawToPrinter
    {
        public void Draw()
        {
            Console.WriteLine ("Drawing");
        }
    }

Although the code compiles cleanly, a single implementation of the Draw() method does not allow us to take unique courses of action based on which interface is obtained from an Octagon object. 

When you implement serveral interfaces that have identical members, you can resovle this sort of name *** using explicit interface implementation syntax. 

    public class Octagon : IDrawToForm, IDrawToMemory, IDrawToPrinter
    {
        //explicitly bind draw() implementations to a given interface
        void IDrawToForm.Draw()
        {
            Console.WriteLine ("Drawing to form");
        }

        void IDrawToMemory.Draw()
        {
            Console.WriteLine ("Drawing to memory");
        }

        void IDrawToPrinter.Draw()
        {
            Console.WriteLine ("Drawing to printer");
        }
    }

 

3. Designing interface hierarchy

Interface can be arranged in an interface hierarchy. Like a class hierarchy, when an interface extends an existing interface, it inherits the abstract members defined by the parent. Of course, derived interfaces never inherit true implementation. Rather, a derived interface simple extends its own definition with addition abstract members. 

 

 

    public interface IDrawable{

        void Draw();
    }

    public interface IAdvancedDraw : IDrawable
    {
        void DrawInBoundingBox(int top, int left, int botto, int right);
        void DrawUpsideDown();
    }

 

Given this design, if a class were to implement IAdvancedDraw, it would now be required to implement each every member defined up the chain of inheritance. 

public class BitmapImage : IAdvancedDraw
    {
        #region IAdvancedDraw implementation

        public void DrawInBoundingBox (int top, int left, int botto, int right)
        {
            Console.WriteLine ("Drawing in a box...");
        }

        public void DrawUpsideDown ()
        {
            Console.WriteLine ("Drawing upside down!");
        }

        #endregion

        #region IDrawable implementation

        public void Draw ()
        {
            Console.WriteLine ("Drawing...");
        }
        #endregion

    }

 

3.1 Multiple Inheritance with interface type

Unlike class types, a single interface can extend multiple base interfaces, allowing us to desing some very powerful and flexible abstraction. 

    public interface IDrawable{

        void Draw();
    }
    public interface IPrintable
    {

        void Print();
        void Draw(); //name *** with method in IDrawable
    }

    public interface IShape : IDrawable, IPrintable
    {
        int GetNumberOfSides();
    }

    public class Rectangle : IShape
    {
        //using explicit implementation to handle member name ***
        void IPrintable.Draw(){
            Console.WriteLine ("Print from IPrintable");
        }

        void IDrawable.Draw(){
            Console.WriteLine ("Print from IDrawable");
        }
        public void Print(){
            Console.WriteLine ("Print");
        }
        public int GetNumberOfSides(){
            return 4;
        }
    }

Do be aware, that interfaces are a fundanmental aspect of the .net framework. Regardless of the type of application you are developing, working with interfaces will be part of the process. 

4. Common used Interfaces

4.1 IEnumerable and IEnumerator

Recall that C# support a keyword name foreach that allows you to iterate over the contents of any array type. While it might seem only arrays can make use of this construct, the truth of the matter is any type supporting a method named GetEnumerator() can be evaluated by the foreach construct. 

    public class Garage
    {
        private Car[] carArray = new Car[4];

        public Garage
        {
            carArray[0] = new Car("car1", 30);
            carArray[1] = new Car("car2", 33);
            carArray[2] = new Car("car3", 40);
            carArray[3] = new Car("car4", 66);
        }
    }

Even thougth the Garage contains array called carArray, it is not possible to use foeach construct because the Garage does not implement a method named GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking withing System.Collections namespace. 

    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }

    public interface IEnumerator
    {
        bool MoveNext();
        object Current {get;} //get current item
        void Rest(); //reset cursor before the first member
    }

The IEnumerator interface provides the infrastructure to allow the caller to traverse the internal objects contained by the IEnumerable-compatible container. If you want to update Garage type to support these interface, you could take the long road and implement each method manually. Or you can simply delegate the request to the System.Array, as it already implements IEnumerable and IEnumerator. 

    public class Garage : IEnumerable
    {
        private Car[] carArray = new Car[4];

        public Garage
        {
            carArray[0] = new Car("car1", 30);
            carArray[1] = new Car("car2", 33);
            carArray[2] = new Car("car3", 40);
            carArray[3] = new Car("car4", 66);
        }

        public IEnumerator GetEumerator()
        {
            return carArray.GetEnumerator();
        }
    }

4.2 Building Iterator Methods with the yield keyword

In earlier versions of the .NET platform, when you wanted to build a custom collection (such as Garage) that supported foeach enumerator, implementing IEnumerable interface was the only option. Now, there is an alternative way to build types that work with the foreach loop via iterators. 

Simply put, an iterator is a member that specifies how a container's internal items should be returned when processed by foreach. While the iterator method must still be named GetEnumerator() and the return value must still be of type IEnumerator. 

    public class Garage : IEnumerable
    {
        private Car[] carArray = new Car[4];
        public Garage
        {
            carArray[0] = new Car("car1", 30);
            carArray[1] = new Car("car2", 33);
            carArray[2] = new Car("car3", 40);
            carArray[3] = new Car("car4", 66);
        }

        public IEnumerator GetEnumerator()
        {
            foreach(Car c in carArray)
            {
                yield return c;
            }
        }

    }

The yield keyword is used to specify the value to be returned to the caller's foreach consturct. When the yield return statement is reached, the current location in the container is stored, and execution is restarted from this location the next time the iteration is called. 

    public IEnumerator GetEnumerator()
        {
            yield return carArray[0];
            yield return carArray[1];
            yield return carArray[2];
            yield return carArray[3];
        }

It is also interesting to note that the yield keyword can technically be used within any method, regardless of its name. When building a named iterator, be very aware that the method will return the IEnumerable interface, rather than IEnumerator-compatible type. 

        public IEnumerable GeTheCars(bool returnRevesed)
        {
            if (returnRevesed) {
                for (int i = carArray.Length; i != 0; i--) {
                    yield return carArray [i - 1];
                }
            } 
            else 
            {
                foreach (Car c in carArray) 
                {
                    yield return c;
                }
            }

        }

4.3 ICloneable Interface

When you want to give your custom type the ability to return an identical copy of itself to the caller, you may implement the standard ICloneable interface. 

        public interface ICloneable
        {
            object Clone();
        }

The basic functionality tends to be the same: copy the values of your member variables into a new object instance of the same type, and return it to the user. If you have  class that contains nothing but value types, implement your clone() method using MemberwiseClone(). If you have a custom type that maintains other reference types, you might want to create a new object that takes into account each reference variable, in order to get a "deep copy". 

4.4 IComparable Interface

The interface specifies a behavior that allows an object to be sorted based on some specified key. 

    public interface IComparable
        {
            int CompareTo(object o);
        }

The System.Array class defines a static method named Sort(). When you invoke this method on an array of intrinsic type (int, short), you are able to sort the item in the array in numeric/alphabetic order, as these intrinsic data types implement IComparable. 

If you wish to sort array of object, the object must implement IComparable interface. 

        int IComparable.CompareTo(object obj)
        {
            Car temp = obj as Car;
            if (temp != null) {
                if (this.CarID > temp.CarID)
                    return 1;
                if (this.CarID < temp.CarID)
                    return -1;
                else
                    return 0;
            } 
            else 
            {
                throw new ArgumentException ("Parameter is not a car");
            }
        }

We can streamline the previous implementation of CompareTo() given the face that the C# int data type implements IComparable. 

    int IComparable.CompareTo(object obj)
        {
            Car temp = obj as Car;
            if (temp != null) {
                return this.CarID.CompareTo (temp.CarID);
            } 
            else 
            {
                throw new ArgumentException ("Parameter is not a car");
            }
        }

4.5 Specifying multiple sort orders with IComparer

    public interface IComparer
        {
            int Compare(object o1, object o2);
        }

Unlike IComparable interface, IComparer is typically not implemented on the type you are trying to sort. Rather, you implement this interface on any number of helper classes, one for each sort order. 

    public class PetNameComparer : IComparer
    {
        int IComparer.Compare(object o1, object o2)
        {
            Car car1 = o1 as Car;
            Car car2 = o2 as Car;

            if (car1 != null && car2 != null) {
                return String.Compare (car1.PetName, car2.PetName);
            } 
            else 
            {
                throw new ArgumentException ("Parameter is not a car !");
            }
        }
        
    }
Array.Sort (cars, new PetNameComparer ());

It is worth pointing out that you can make use of a custom static property in order to help the obejct users along when sorting your Car types by a specific data point. 

 

posted @ 2015-03-25 21:14  tim_bo  阅读(154)  评论(0)    收藏  举报