Structs (Value Types) in C#: Working with Interfaces

1. Working with Interfaces

Typically, code doesn’t know whether an object supports an interface, so it needs to check whether the object implements the interface before doing the cast:

using System; interface IScalable {

void ScaleX(float factor);

void ScaleY(float factor);

}

public class DiagramObject {

public DiagramObject() {}

}

public class TextObject: DiagramObject, IScalable {

public TextObject(string text)
{

this.text = text;

}

// implementing ISclalable.ScaleX()

public void ScaleX(float factor)

{

Console.WriteLine(“ScaleX: {0} {1}”, text, factor);

// scale the object here.

}

// implementing IScalable.ScaleY()

public void ScaleY(float factor)

{

Console.WriteLine(“ScaleY: {0} {1}”, text, factor);

// scale the object here.

}

private string text;

}

class Test {

public static void Main()

{

DiagramObject[] dArray = new DiagramObject[100];

dArray[0] = new DiagramObject();

dArray[1] = new TextObject(“Text Dude”);

dArray[2] = new TextObject(“Text Backup”);

// array gets initialized here, with classes that

// derive from DiagramObject. Some of them implement

// IScalable.

foreach (DiagramObject d in dArray)

{

if (d is IScalable)

{

IScalable scalable = (IScalable) d;

scalable.ScaleX(o.lF);

scalable.ScaleY(lO.OF);

}

}

}

}

Before the cast is done, it’s checked to make sure the cast will succeed. If it will succeed, the object is cast to the interface, and the scale functions are called.

This construct unfortunately checks the type of the object twice: once as part of the is operator and once as part of the cast. This is wasteful, since the cast can never fail.

One way around this is to restructure the code with exception handling, but that’s not a great idea because it’d make the code more complex, and exception handling should generally be reserved for exceptional conditions. It’s also not clear whether it’d be faster, since exception handling has some overhead.

2. The as Operator

C# provides a special operator for this situation, the as operator. Using the as operator, you can rewrite the loop as follows:

using System;

interface IScalable {

void ScaleX(float factor);

void ScaleY(float factor);

}

public class DiagramObject {

public DiagramObject() {}

}

public class TextObject: DiagramObject, IScalable {

public TextObject(string text)

{

this.text = text;

}

// implementing IScalable.ScaleX()

public void ScaleX(float factor)

{

Console.WriteLine(“ScaleX: {0} {1}”, text, factor);

// scale the object here.

}

// implementing IScalable.ScaleY() public void ScaleY(float factor)

{

Console.WriteLine(“ScaleY: {0} {1}”, text, factor);

// scale the object here.

}

private string text;

}

class Test {

public static void Main()

{

DiagramObject[] dArray = new DiagramObject[l00];

dArray[0] = new DiagramObject();

dArray[1] = new TextObject(“Text Dude”);

dArray[2] = new TextObject(“Text Backup”);

// array gets initialized here, with classes that

// derive from DiagramObject. Some of them implement

// IScalable.

foreach (DiagramObject d in dArray)

{

IScalable scalable = d as IScalable;

if (scalable != null)

{

scalable.ScaleX(o.lF);

scalable.ScaleY(lO.OF);

}

}

}

}

The as operator checks the type of the left operand, and if it can be converted explicitly to the right operand, the result of the operator is the object converted to the right operand. If the conversion will fail, the operator returns null.

Both the is and as operators can also be used with classes.

3. Interfaces and Inheritance

When converting from an object to an interface, the inheritance hierarchy is searched until it finds a class that lists the interface on its base list. Having the right functions alone isn’t enough. For example:

using System; interface IHelper {

void HelpMeNow();

}

public class Base: IHelper {

public void HelpMeNow()

{

Console.WriteLine(“Base.HelpMeNow()”);

}

}

// Does not implement IHelper, though it has the right

// form.

public class Derived: Base {

public new void HelpMeNow()

{

Console.WriteLine(“Derived.HelpMeNow()”);

}

}

class Test {

public static void Main()

{

Derived der = new Derived();

der.HelpMeNow();

IHelper helper = (IHelper) der;

helper.HelpMeNow();

}

}

This code gives the following output:

Derived.HelpMeNow()

Base.HelpMeNow()

It doesn’t call the Derived version of HelpMeNow() when calling through the interface, even though Derived does have a function of the correct form, because Derived doesn’t implement the interface.

4. Design Guidelines

Both interfaces and abstract classes have similar behaviors and can be used in similar situations. Because of how they work, however, interfaces make sense in some situations and abstract classes in others. This section contains a few guidelines to determine whether a capability should be expressed as an interface or an abstract class.

The first thing to check is whether the object would be properly expressed using the “is-a” relationship. In other words, is the capability an object, and would the derived classes be examples of that object?

Another way of looking at this is to list what kind of objects would want to use this capability. If the capability would be useful across a range of different objects that aren’t really related to each other, an interface is the proper choice.

When using interfaces, remember that there’s no versioning support for an interface. If a function is added to an interface after users are already using it, their code will break at runtime and their classes won’t properly implement the interface until the appropriate modifications are made.

5. Multiple Implementation

Unlike object inheritance, a class can implement more than one interface:

interface IFoo {

void ExecuteFoo();

}

interface IBar {

void ExecuteBar();

}

class Tester: IFoo, IBar {

public void ExecuteFoo() {}

public void ExecuteBar() {}

}

This works fine if there are no name collisions between the functions in the interfaces. But if the example is just a bit different, there might be a problem:

// error interface IFoo {

void Execute();

}

interface IBar {

void Execute();

}

class Tester: IFoo, IBar {

// IFoo or IBar implementation?

public void Execute() {}

}

Does Tester.Execute() implement IFoo.Execute() or IBar.Execute()?

In this example, IFoo.Execute() and IBar.Execute() are implemented by the same function. If they’re supposed to be separate, one of the member names could be changed, but that’s not a good solution in most cases.

More seriously, if IFoo and IBar came from different vendors, they couldn’t be changed. The .NET runtime and C# support a technique known as explicit interface implementation, which allows a function to specify which interface member it’s implementing.

6. Explicit Interface Implementation

To specify which interface a member function is implementing, qualify the member function by putting the interface name in front of the member name.

Here’s the previous example, revised to use explicit interface implementation:

using System; interface IFoo {

void Execute();

}

interface IBar {

void Execute();

}

class Tester: IFoo, IBar {

void IFoo.Execute()

{

Console.WriteLine(“IFoo.Execute implementation”);

}

void IBar.Execute()

{

Console.WriteLine(“IBar.Execute implementation”);

}

}

class Test {

public static void Main()

{

Tester tester = new Tester();

IFoo iFoo = (IFoo) tester;

iFoo.Execute();

IBar iBar = (IBar) tester;

iBar.Execute();

}

}

This prints the following:

IFoo.Execute implementation

IBar.Execute implementation

This is what you expected. But what does the following test class do?

// error

using System;

interface IFoo {

void Execute();

}

interface IBar {

void Execute();

}

class Tester: IFoo, IBar {

void IFoo.Execute()

{

Console.WriteLine(“IFoo.Execute implementation”);

}

void IBar.Execute()

{

Console.WriteLine(“IBar.Execute implementation”);

}

}

class Test {

public static void Main()

{

Tester tester = new Tester();

tester.Execute();

}

}

Is IFoo.Execute() called, or is IBar.Execute() called?

The answer is that neither is called. There’s no access modifier on the implementations of IFoo.Execute() and IBar.Execute() in the Tester class, and therefore the functions are private and can’t be called.

In this case, this behavior isn’t because the public modifier wasn’t used on the function; it’s because access modifiers are prohibited on explicit interface implementations, so the only way the interface can be accessed is by casting the object to the appropriate interface.

To expose one of the functions, add a forwarding function to Tester:

using System;

interface IFoo {

void Execute();

}

interface IBar {

void Execute();

}

class Tester: IFoo, IBar {

void IFoo.Execute()

{

Console.WriteLine(“IFoo.Execute implementation”);

}

void IBar.Execute()

{

Console.WriteLine(“IBar.Execute implementation”);

}

public void Execute()

{

((IFoo) this).Execute();

}

}

class Test {

public static void Main()

{

Tester tester = new Tester();

tester.Execute();

}

}

Now, calling the Execute() function on an instance of Tester will forward to Tester.IFoo.Execute().

You can use this hiding for other purposes, as detailed in the next section.

7. Implementation Hiding

Sometimes it makes sense to hide the implementation of an interface from the users of a class, either because it’s not generally useful or because you just want to reduce the member clutter. Doing so can make an object much easier to use. For example:

using System; class DrawingSurface {

}

interface IRenderIcon {

void DrawIcon(DrawingSurface surface, int x, int y);

void DragIcon(DrawingSurface surface, int x, int y, int x2, int y2);

void ResizeIcon(DrawingSurface surface, int xsize, int ysize);

}

class Employee: IRenderIcon {

public Employee(int id, string name)

{

this.id = id;

this.name = name;

}

void IRenderIcon.DrawIcon(DrawingSurface surface, int x, int y)

{

}

void IRenderIcon.DragIcon(DrawingSurface surface, int x, int y,int x2, int y2)

{

}

void IRenderIcon.ResizeIcon(DrawingSurface surface, int xsize, int ysize)

{

}

int id;

string name;

}

If the interface had been implemented normally, the DrawIcon(), DragIcon(), and ResizeIcon () member functions would be visible as part of Employee, which might be confusing to users of the class. By implementing them through explicit implementation, they can be accessed only through the interface.

8. Interfaces Based on Interfaces

You can combine interfaces to form new interfaces. The ISortable and ISerializable interfaces can be combined, and new interface members can be added:

using System.Runtime.Serialization;

using System;

interface IComparableSerializable :

IComparable, ISerializable

{

string GetStatusString();

}

A class that implements IComparableSerializable would need to implement all the members in IComparable, ISerializable, and the GetStatusString() function introduced in IComparableSerializable.

9. Interfaces and Structs

Like classes, structs can also implement interfaces. Here’s a short example: using System;

struct Number: IComparable {

int value;

public Number(int value)

{

this.value = value;

}

public int CompareTo(object obj2)

{

Number num2 = (Number) obj2;

if (value < num2.value)

return(-1);

else if (value > num2.value)

return(1);

else

return(0);

}

}

class Test {

public static void Main()

{

Number x = new Number(33);

Number y = new Number(34);

IComparable Ic = (IComparable) x;

Console.WriteLine(“x compared to y = {0}”, Ic.CompareTo(y));

}

}

This struct implements the IComparable interface, which compares the values of two elements for sorting or searching operations.

Like classes, interfaces are reference types, so there’s a boxing operation involved here. When a value type is cast to an interface, the value type is boxed and the interface pointer is to the boxed value type.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *