简体   繁体   中英

“Interface not implemented” when Returning Derived Type

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.

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.

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

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 .

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<> .

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();
    }
}

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; } }
}

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.

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.

I have run into similar situations and want to give a more concrete example of why this is not allowed. Generic parts of my app deal with an interface that contains properties, and provides data through that interface to another part of the application that contains concrete classes implementing these interfaces.

The goal I think is similar to yours: the conrete classes to have more functionality, and the interface provides the absolute minimum needed for the generic part of the application. It is a good practice for the Interface to expose the least amount of functionality needed, because it maximizes compatibility/reuseability. Ie it doesn't require an implementer of the interface to implement more than is needed.

However consider if you had a setter on that property. You create an instance of your concrete class, and pass it to some generic helper that takes an ISomeData. In that helper's code, they are working with an ISomeData. Without any type of generic T where T:new() or factory pattern, they can't create new instances to go into Data that match your concrete implementation. They simply return a list that implements IEnumerable:

instanceISomeData.Data = new SomeOtherTypeImplementingIEnumerable();

If SomeOtherTypeImplementingIEnumerable doesn't inherit List, but you've implemented .Data as a List, then this is an invalid assignment. If the compiler allowed you to do this, then scenarios like this would crash at runtime because SomeOtherTypeImplementingIEnumerable can't be cast to List. However, in the context of this helper working with ISomeData, it hasn't violated the ISomeData interface in anyway, and the assignment of any type supporting IEnumerable to .Data should be valid. So your implementation, if the compiler allowed it, could break perfectly good code working with the Interface.

This is why you can't implement .Data as a derived type. You are more tightly constraining the implementation of .Data to only except List, instead of any IEnumerable. Thus while the interface says "any IEnumerable is allowed", your concrete implementation would cause pre-existing code supporting ISomeData to suddenly break when working with your implementation of the interface.

Of course you don't really run into this scenario with only a getter, but it would complicate things to allow it in get scenarios but not otherwise.

I usually go with Jake's solution or Alioto's solution, depending on how picky I am feeling at the moment.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM