Veldaeven
Veldaeven

Reputation: 329

Interface Implementation Philosophy

In C#, you are not allowed to implement an interface like this:

public interface ITest
{
    IEnumerable<int> Numbers { get; set; }
}

public class CTest : ITest
{
    public List<int> Numbers { get; set; }
}

My question is: Is there some software philosophy that makes this type of implementation wrong? Is it a contested paradigm, sort of like multiple inheritance, where arguments have been taking place for decades?

I can't see any reason why such an interface implementation wouldn't work just fine. If you know about ITest, then you only get to see the IEnumerable<int> part of CTest.Numbers. If you know about CTest, then you can use the entire List<int> implementation. Since List<int> derives from IEnumerable<int>, why wouldn't it satisfy the Interface requirements?

Upvotes: 2

Views: 89

Answers (2)

Lee
Lee

Reputation: 144206

The ability of subclasses to return a subtype when overriding base methods is called return type covariance

In this case your example is unsafe due to the setter for the Numbers property:

ITest t = new CTest();
t.Numbers = new HashSet<int>();

If there were only a getter defined in ITest then it would be safe, although C# still does not allow it - Java does however:

public interface Test {
    Iterable<Integer> getNumbers();
}

public class CTest implements Test {
    public List<Integer> getNumbers() {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        return numbers;
    }
}

Upvotes: 9

D Stanley
D Stanley

Reputation: 152634

An interface is a contract. It tells clients exactly what methods/properties will be available and exactly what types the inputs and outputs are.

When you tell a client that any class that implements ITest will accept any IEnumerable<int> for the value of Numbers, but the CTest class will only accept a List<int>, you have broken that contract.

If you want to implement the interface but also expose a more specific collection type you can explicitly implement the interface, but then you have to decide what to do is a client tries to "set" an IEnumerable<int> that is not a List<int> one option is just to create a List<int> behind-the-scenes:

public class CTest : ITest
{
    public List<int> Numbers { get; set; }

    // satisfies the interface
    IEnumerable<int> ITest.Numbers { 
        get { return Numbers; } 
        set { Numbers = new List<int>(value); } 
    }

}

Now clients that are aware of CTest can use the List<int> property, but clients that only know of the interface can still set the collection without casting.

Upvotes: 3

Related Questions