“Interface not implemented” when Returning Derived Type

前端 未结 12 641
野趣味
野趣味 2020-11-27 21:11

The following code:

public interface ISomeData
{
    IEnumerable Data { get; }
}

public class MyData : ISomeData
{
    private List

        
相关标签:
12条回答
  • 2020-11-27 22:01

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

    0 讨论(0)
  • 2020-11-27 22:02

    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.

    0 讨论(0)
  • 2020-11-27 22:04

    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;
              }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-27 22:08

    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.

    0 讨论(0)
  • 2020-11-27 22:12

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

    0 讨论(0)
  • 2020-11-27 22:13

    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; } }
    }
    
    0 讨论(0)
提交回复
热议问题