"Interface not implemented" when Returning Derived Type

Viewed 8607

The following code:

public interface ISomeData
{
    IEnumerable<string> Data { get; }
}

public class MyData : ISomeData
{
    private List<string> m_MyData = new List<string>();
    public List<string> Data { get { return m_MyData; } }
}

Produces the following error:

error CS0738: 'InheritanceTest.MyData' does not implement interface member 'InheritanceTest.ISomeData.Data'. 'InheritanceTest.MyData.Data' cannot implement 'InheritanceTest.ISomeData.Data' because it does not have the matching return type of 'System.Collections.Generic.IEnumerable'.

Since a List<T> implements IEnumerable<T>, one would think that my class would implement the interface. Can someone explain what the rationale is for this not compiling?

As I can see it, there are two possible solutions:

  1. Change the interface to be more specific and require IList be implemented.
  2. Change my class (MyData) to return IEnumerable and implement the original interface.

Now suppose I also have the following code:

public class ConsumerA
{
    static void IterateOverCollection(ISomeData data)
    {
        foreach (string prop in data.MyData)
        {
            /*do stuff*/
        }
    }
}

public class ConsumerB
{
    static void RandomAccess(MyData data)
    {

        data.Data[1] = "this line is invalid if MyPropList return an IEnumerable<string>";
    }
}

I could change my interface to require IList to be implemented (option 1), but that limits who can implement the interface and the number of classes that can be passed into ConsumerA. Or, I could change implementation (class MyData) so that it returns an IEnumerable instead of a List (option 2), but then ConsumerB would have to be rewritten.

This seems to be a shortcoming of C# unless someone can enlighten me.

12 Answers

Unfortunately, the return type must match. What you are looking for is called 'return type covariance' and C# doesn't support that.

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=90909

Eric Lippert, senior developer on C# Compiler team, mentions on his blog that they don't plan to support return type covariance.

"That kind of variance is called "return type covariance". As I mentioned early on in this series, (a) this series is not about that kind of variance, and (b) we have no plans to implement that kind of variance in C#. "

http://blogs.msdn.com/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx

It's worth reading Eric's articles on covariance and contravariance.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

For what you want to do you'll probably want to implement the interface explicitly with a class (not interface) member that returns the List instead of IEnumerable...

public class MyData : ISomeData
{
    private List<string> m_MyData = new List<string>();
    public List<string> Data
    {
        get
        {
            return m_MyData;
        }
    }

    #region ISomeData Members

    IEnumerable<string> ISomeData.Data
    {
        get
        {
            return Data.AsEnumerable<string>();
        }
    }

    #endregion
}

Edit: For clarification, this lets the MyData class return a List when it is being treated as an instance of MyData; while still allowing it to return an instance of IEnumerable when being treated as an instance of ISomeData.

What if you accessed your MyData object trough the ISomeData interface? In that case, IEnumerable could be of an underlying type not assignable to a List.

IEnumerable<string> iss = null;

List<string> ss = iss; //compiler error

EDIT:

I understand what you mean from your comments.

Anyway, what I would do in your case would be:

    public interface ISomeData<T> where T: IEnumerable<string>
    {
        T Data { get; }
    }

    public class MyData : ISomeData<List<string>>
    {
        private List<string> m_MyData = new List<string>();
        public List<string> Data { get { return m_MyData; } }
    }

Converting to generic Interface with appropriate constraint offers I think the best of both flexibility and readability.

The signature of the member can't be different.

You can still return the List<string> within the get method, but the signature needs to be the same as the interface.

So simply change:

public List<string> Data { get { return m_MyData; } }

to

public IEnumerable<string> Data { get { return m_MyData; } }

Regarding your other option: changing the interface to return a List. This should be avoided. It is poor encapsulation and is regarded as a code smell.

You could implement like this:

public class MyData : ISomeData
{
    private List<string> m_MyData = new List<string>();
    public IEnumerable<string> Data { get { return m_MyData; } }
}

Interfaces require that the signature of the method match with the signature of the contract exactly.

Here is a simpler example that also will not compile:

interface IFoo
{
    Object GetFoo();
}

class Foo : IFoo
{
    public String GetFoo() { return ""; }
}

Now as for what to do about it I would let the interface dictate the implementation. If you want the contract to be IEnumerable<T> then that is what it should be in the class as well. The interface is the most important thing here as the implementation is free to be as flexible as it needs to be.

Just be certain that IEnumerable<T> is the best choice here. (This is all highly subjective as I don't know much about your domain or the purpose of these types in your application. Good luck!)

Why not just return a List from your interface ...

public interface ISomeData
{
    List<string> Data { get; }
}

If you know your consumers are going to both iterate over it (IEnumerable) and add to it (IList) then it seems logical to simply return a List<>.

Hmm, is it a shortcoming, I would say no.

In either way, I would work around it like darin's answer, or, if you explicitly want a List accessor as well, you could do it like this:

public class MyData : ISomeData
{

    IEnumerable<string> ISomeData.Data
    {
        get
        {
              return _myData;
        }
    }

    public List<string> Data
    {
          get
          {
             return (List<string>)((ISomeData)this).Data;
          }
    }

}

I would choose option 2:

The point to define an interface in your code is to define an contract, and so you and other people who implement your interface know what to agree on. Whether you define IEnumerable or List in your interface is really an contract issue and belong to framework design guideline. Here is a whole book to discuss this.

Personally , I would expose IEnumerable and implement MyData to IEnumerable, and you can cast it back to List in RandomAccess() method.

What if you changed your interface to extend IEnumerable so you can enumerate the object and edit the data via the class property.

public interface ISomeData : IEnumerable<string>
{
    IEnumerable<string> Data { get; }
}

public class MyData : ISomeData
{
    private List<string> m_MyData = new List<string>();
    public List<string> Data { get { return m_MyData; }

    public IEnumerator<string> GetEnumerator()
    {
        return Data;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

If you need to have a list in your interface (known number of items with random access), you should then consider changing the interface to

public interface ISomeData
{
    ICollection<string> Data { get; } 
}

This will give you both the extensibility and the features you need from a list.

List<T> cannot be easily subclassed, meaning that you might have trouble returning that exact type from all the classes that want to implement your interface.

ICollection<T> on the other hand, can be implemented in various ways.

Related