C Sharp

Avoiding Name Ambiguity

One of the main reasons that C# does not support multiple inheritance is the problem of name collision, which results from name ambiguity. Although C# does not support multiple inheritance at the object level (derivation from a class), it does support inheritance from one class and the additional implementation of multiple interfaces. However, with this power comes a price: name collision.

In the following example, we have two interfaces, ISerializable and IDataStore, which support the reading and storing of data in two different formats-one as an object to disk in binary form and the other to a database. The problem is that they both contain methods named SaveData: -

using System;
interface ISerializable
{
    void SaveData();
}
interface IDataStore
{
    void SaveData();
}
class Test : ISerializable, IDataStore
{
    public void SaveData()
    {
        Console.WriteLine("Test.SaveData called");
    }
}
class NameCollisions1App
{
    public static void Main()
    {
        Test test = new Test();
        Console.WriteLine("Calling Test.SaveData()");
        test.SaveData();
    }
}

At the time of this writing, this code does compile. However, I'm told that in a future build of the C# compiler, the code will result in a compile-time error because of the ambiguity of the implemented SaveData method. Regardless of whether this code compiles, you'd have a problem at run time because the resulting behavior of calling the SaveData method would not be clear to the programmer attempting to use the class. Would you get the SaveData that serializes the object to disk, or would you get the SaveData that saves to a database? -

In addition, take a look at the following code: -

using System;
interface ISerializable
{
    void SaveData();
}
interface IDataStore
{
    void SaveData();
}
class Test : ISerializable, IDataStore
{
    public void SaveData()
    {
        Console.WriteLine("Test.SaveData called");
    }
}
class NameCollisions2App
{
    public static void Main()
    {
        Test test = new Test();
        if (test is ISerializable)
        {
            Console.WriteLine("ISerializable is implemented");
        }
        if (test is IDataStore)
        {
            Console.WriteLine("IDataStore is implemented");
        }
    }
}

Here, the is operator succeeds for both interfaces, which indicates that both interfaces are implemented although we know that not to be the case! Even the compiler gives the following warnings upon compilation of this example: -

NameCollisions2.cs(27,7): warning CS0183: The given expression is
always of the provided ('ISerializable') type
NameCollisions2.cs(32,7): warning CS0183: The given expression is
always of the provided ('IDataStore') type

The problem is that the class has implemented either a serialized version or a database version of the Bind method (not both). However, if the client checks for the implementation of one of the interfaces-both will succeed-and happens to try to use the one that wasn't truly implemented, unexpected results will occur.

You can turn to explicit member name qualification to get around this problem: remove the access modifier, and prepend the member name-SaveData, in this case-with the interface name: -

using System;
interface ISerializable
{
    void SaveData();
}
interface IDataStore
{
    void SaveData();
}
class Test : ISerializable, IDataStore
{
    void ISerializable.SaveData()
    {
        Console.WriteLine("Test.ISerializable.SaveData called");
    }
    void IDataStore.SaveData()
    {
        Console.WriteLine("Test.IDataStore.SaveData called");
    }
}
class NameCollisions3App
{
    public static void Main()
    {
        Test test = new Test();
        if (test is ISerializable)
        {
            Console.WriteLine("ISerializable is implemented");
            ((ISerializable)test).SaveData();
        }
        Console.WriteLine();
        if (test is IDataStore)
        {
            Console.WriteLine("IDataStore is implemented");
            ((IDataStore)test).SaveData();
        }
    }
}

Now there's no ambiguity as to which method will be called. Both methods are implemented with their fully qualified names, and the resulting output from this application is what you'd expect: -

ISerializable is implemented
Test.ISerializable.SaveData called
IDataStore is implemented
Test.IDataStore.SaveData called