Bogotobogo
contact@bogotobogo.com
Bookmark and Share






Interfaces

csharp_logo



Interfaces

An interface is similar to a class, but it provides a specification rather than an implementation for its members. An interface is special in the following ways:

  • A class can implement multiple interfaces. In contrast, a class can inherit from only a single class.
  • Interface members are all implicitly abstract. In contrast, a class can provide both abstract members and concrete members with implementations.
  • Structs can implement interfaces. In contrast a struct cannot inherit from a class.


Interfaces vs. Abstract Classes

The interface type seems very similar to an abstract base class. When a class is market as abstract, it may define any number of abstract members to provide a polymorphic interface to all derived types. But even when a class type does define a set of abstract members, it may define any number of constructors, field data, nonabstract members, and so on. In contrast, interfaces only contain abstract members.

The polymorphic interface established by an abstract parent class suffers from one major limitation in that only derived types support the members defined by the abstract parent. But in larger software, it is very common to develop multiple class hierarchies that have no common parent beyond System.Object. Because abstract members in an abstract base class only apply to derived types, we have no way to configure types in different hierarchies to support same polymorphic interface.


Your Ad Here

Let's take a look at the following example:

abstract class ColoneableType
{
	public abstract object Clone();
}

Only the members that extend CloneableType can support the Clone() method. If we create a new class that does not extend this base class, we cannot gain this polymorphic interface. This is one of the areas that the interface types can be useful. Once an interface has been defined, it can be implemented by any type, in any hierarchy, which any namespaces or any assembly. So, we can say that interfaces are highly polymorphic.

Consider the standard .NET interface IColneable defined in the System namespace:

public Interface ICloneable
{
	object Clone();
}

Seemingly unrelated types such as System.Array and System.Data et al. are all implementing the ICloneable interface. Although these types have no common parent other than System.Object, we can treat them polymorphically via the ICloneable interface type.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Interfaces
{
    class Program
    {
        static void Main(string[] args)
        {
            string myString = "Hello Interfaces";
            OperatingSystem MacOS = new OperatingSystem(PlatformID.MacOSX, new Version());
            System.Data.SqlClient.SqlConnection sqlCon = new System.Data.SqlClient.SqlConnection();

            CloneMe(myString);
            CloneMe(MacOS);
            CloneMe(sqlCon);
            Console.ReadLine();
        }

        private static void CloneMe(ICloneable c)
        {
            object theClone = c.Clone();
            Console.WriteLine("colne is a: {0}", theClone.GetType().Name);
        }
    }
}

Out we get:

colne is a: String
colne is a: OperatingSystem
colne is a: SqlConnection

In the example, we have a method CloneMe() that took an ICloneable interface parameter. So, we could pass this method any object that support that interface.



Defining Interfaces

Interface declaration is like a class declaration, but it provides no implementation for its members, since all its members are implicitly abstract. These members will be implemented by the classes and structs that implement the interface. In interface can contain only methods, properties, event, and indexers, which noncoincidentally are precisely the members of a class that can be abstract.

Let's start with an example.

public Interface IPoint
{
	byte GetNumberOfPoints();
}

When we define interface members, we do not define an implementation for the member. Interfaces are just a protocol, and never define an implementation. So, the following version of IPoint gives compile errors:

public Interface IPoint
{
	// Error! Interfaces cannot have fields.
	public int numbOfPoints; 

	// Error! Interface don't have constructors.
	public IPoint() { numberOfPoints = 0; }

	// Error! Interface don't provide an implementation.
	byte GetNumberOfPoints() { return numbOfPoints; }
}

Interfaces are nothing more than a named collection of abstract members. So, we cannot allocate interface type as we would a class or a structure:

static void Main(string[] args)
{
	// Error!
	IPoint ptr = new IPoint(); 
}

Interfaces are quite useless on their own. They don't bring much to the table until they are implemented by a class or structure.



Interface Members

Here is the shapes hierarchy with interfaces we're going to use.


shapesHierarchy

Here are files for the class hierarchy diagram:

// IPoint.cs

namespace CustomInterface
{
    // The IPoint behavior as a read-only property
    public interface IPoint
    {
        byte Points { get; }
    }
}
// Circle.cs

using System;

namespace CustomInterface
{
    public class Circle : Shape
    {
        public Circle() { }
        public Circle(string name) : base(name) { }
        public override void Draw()
        {
            Console.WriteLine("Drawing {0} the Circle", shapeName);
        }
    }
}
// ThreeDCircle

using System;

namespace CustomInterface
{
    public class ThreeDCircle : Circle
    {
        public void Draw()
        {
            Console.WriteLine("Drawing a 3D Circle");
        }
    }
}
// Triangle.cs

using System;

namespace CustomInterface
{
    public class Triangle : Shape, IPoint
    {
        public Triangle() { }
        public Triangle(string name) : base(name) { }
        public override void Draw()
        {
            Console.WriteLine("Drawing {0} the Triangle", PetName);
        }

        public byte Points
        {
            get { return 3; }
        }
    }
}
// Hexagon.cs

using System;

namespace CustomInterface
{
    public class Hexagon : Shape, IPoint
    {
        public Hexagon() { }
        public Hexagon(string name) : base(name) { }
        public override void Draw()
        {
            Console.WriteLine("Drawing {0} the Hexagon", PetName);
        }

        public byte Points
        {
            get { return 6; }
        }
    }
}
// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CustomInterface
{
    public abstract class Shape
    {
        protected string shapeName;

        public Shape()
        {
            shapeName = "NoName";
        }

        public Shape(string s)
        {
            shapeName = s;
        }

        public virtual void Draw()
        {
            Console.WriteLine("Shape.Draw()");
        }

        public string PetName
        {
            get { return shapeName; }
            set { shapeName = value; }
        }

        static void Main(string[] args)
        {
        }
    }
}

Now we have a set of types that support IPoint interface. Next question is how we interact with the new interface. Let's invoke the methods of interface directly from the object level. First, look at the following Main() method:

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

Output from the run is:

Points: 6

That works well because the Hexagon class implements the IPoint interface and it has a Points property. However, in cases when we have lots of Shape-compatible types, and only some of which support IPoint, there is a chance to get an error if we invoke the Point property on a class that has not implemented IPoint.

So, the natural question is how can we dynamically determine the set of interfaces that support IPoint?

One way to determine at runtime whether a type supports a specific interface is to use an explicit case. In case when the type does not support the requested interface, we receive an InvalidCastException:

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

	Circle cir = new Circle("Voldemort");
	IPoint ptr = null;

	try
	{
		ptr = (IPoint)cir;
		Console.WriteLine(ptr.Points);
	}
	catch (InvalidCastException e)
	{
		Console.WriteLine(e.Message);
	}
	Console.ReadLine();
}

Output is:

Points: 6
Unable to cast object of type 'CustomInterface.Circle' to type 'CustomInterface.
IPoint'.

Though the code above worked, it would be even better if we can determine which interfaces are supported before invoking the interface members in the first place. So, the next two sections will show the other ways of doing it.



as Keyword

Another way of determining whether a class supports a given interface is to use as keyword.

If the object can be treated as the specified interface, it returns a reference to the interface, if not, it returns a null reference.

static void Main(string[] args)
{
	Hexagon hex = new Hexagon("Voldemort");
	IPoint pHex = hex as IPoint;

	if (pHex != null)
		Console.WriteLine("Points: {0}", hex.Points);
	else
		Console.WriteLine("Hexagon: IPoint not implemented");

	Console.ReadLine();
}


is Keyword

In this section, we're going to use is keyword. If the object is not compatible with the interface, the value false is returned. But if the type is compatible with the interface, we can safely call the members of the interface.

        static void Main(string[] args)
        {
            Shape[] s = { new Hexagon(), new Circle(), 
                            new Triangle("Hermione"), new Circle("Ron")};
            for (int i = 0; i < s.Length; i++)
            {
                s[i].Draw();
                if (s[i] is IPoint)
                    Console.WriteLine("Points: {0}", ((IPoint)s[i]).Points);
                else
                    Console.WriteLine("{0} is not support IPoint", s[i].PetName);
            }

            Console.ReadLine();
        }

Output is:

Drawing NoName the Hexagon
Points: 6
Drawing NoName the Circle
NoName is not support IPoint
Drawing Hermione the Triangle
Points: 3
Drawing Ron the Circle
Ron is not support IPoint


Interfaces As Parameters

We can construct methods that take interface as parameters.

Here are some updates to our example:

// IDraw3D.cs

using System;

namespace CustomInterface
{
    public interface IDraw3D
    {
        void Draw3D();
    }
}
// Circle.cs

using System;

namespace CustomInterface
{
    public class Circle : Shape, IDraw3D
    {
        public Circle() { }
        public Circle(string name) : base(name) { }
        
        public override void Draw()
        {
            Console.WriteLine("Drawing {0} the Circle", shapeName);
        }

        public void Draw3D()
        {
            Console.WriteLine(" Drawing Circle in 3D");
        }
    }
}
// Hexagon.cs

using System;

namespace CustomInterface
{
    public class Hexagon : Shape, IPoint, IDraw3D
    {
        public Hexagon() { }
        public Hexagon(string name) : base(name) { }

        public override void Draw()
        {
            Console.WriteLine("Drawing {0} the Hexagon", PetName);
        }

        public void Draw3D()
        {
            Console.WriteLine(" Drawing Hexagon in 3D");
        }

        public byte Points
        {
            get { return 6; }
        }
    }
}
// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CustomInterface
{
    public abstract class Shape
    {
        protected string shapeName;

        public Shape()
        {
            shapeName = "NoName";
        }

        public Shape(string s)
        {
            shapeName = s;
        }

        public virtual void Draw()
        {
            Console.WriteLine("Shape.Draw()");
        }

        public string PetName
        {
            get { return shapeName; }
            set { shapeName = value; }
        }

        static void DrawIn3D(IDraw3D i3d)
        {
            Console.WriteLine("Drawing IDraw3D compatible type");
            i3d.Draw3D();
        }

        static void Main(string[] args)
        {
            Shape[] s = { new Hexagon(), new Circle(), 
                            new Triangle(), new Circle("Hermione")};
            for (int i = 0; i < s.Length; i++)
            {
                s[i].Draw();
                if (s[i] is IDraw3D)
                    DrawIn3D((IDraw3D)s[i]);
            }

            Console.ReadLine();
        }
    }
}

Here is the updated shapes hierarchy with interfaces we're going to use.


shapesHierarchy2

If we define a method taking an IDraw3D interface as a parameter, we can send in any object implementing IDraw3D.

Output from the run is:

Drawing NoName the Hexagon
Drawing IDraw3D compatible type
 Drawing Hexagon in 3D
Drawing NoName the Circle
Drawing IDraw3D compatible type
 Drawing Circle in 3D
Drawing NoName the Triangle
Drawing Hermione the Circle
Drawing IDraw3D compatible type
 Drawing Circle in 3D

Note that the Triangle type is not drawing in 3D because it is not IDraw3D-compatible.



Interfaces As Return Values

Interfaces can be used as method return values. In the following example, we write a method that takes any System.Object, checks for IPoint compatibility, and then returns a reference to the extracted interface if supported.

// Shape.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CustomInterface
{
    public abstract class Shape
    {
        protected string shapeName;

        public Shape()
        {
            shapeName = "NoName";
        }

        public Shape(string s)
        {
            shapeName = s;
        }

        public virtual void Draw()
        {
            Console.WriteLine("Shape.Draw()");
        }

        public string PetName
        {
            get { return shapeName; }
            set { shapeName = value; }
        }

        static void DrawIn3D(IDraw3D i3d)
        {
            Console.WriteLine("Drawing IDraw3D compatible type");
            i3d.Draw3D();
        }

        static IPoint ExtractPointness(object o)
        {
            if (o is IPoint)
                return (IPoint)o;
            else
                return null;
        }

        static void Main(string[] args)
        {
            int[] myInts = { 100, 200, 300 };
            IPoint pRef = ExtractPointness(myInts);
            if (pRef != null)
                Console.WriteLine("Object has {0} points.", pRef.Points);
            else
                Console.WriteLine("This object does not implement IPoint.");

            Console.ReadLine();
        }
    }
}

Output is:

This object does not implement IPoint.


BakDamSa



List of C# 4.0 Tutorials