c# - «Интерфейс не реализован» при возврате производного типа




(11)

Следующий код:

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

Выдает следующую ошибку:

ошибка CS0738: «InheritanceTest.MyData» не реализует член интерфейса «InheritanceTest.ISomeData.Data». «InheritanceTest.MyData.Data» не может реализовать «InheritanceTest.ISomeData.Data», потому что у него нет соответствующего типа возврата «System.Collections.Generic.IEnumerable».

Поскольку List <T> реализует IEnumerable <T>, можно подумать, что мой класс реализует интерфейс. Может ли кто-нибудь объяснить, что такое обоснование для этого не компиляции?

Как я вижу, есть два возможных решения:

  1. Измените интерфейс, чтобы быть более конкретным, и требуйте, чтобы IList был реализован.
  2. Измените мой класс (MyData), чтобы вернуть IEnumerable и реализовать оригинальный интерфейс.

Теперь предположим, что у меня также есть следующий код:

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

Я мог бы изменить свой интерфейс, чтобы требовать реализации IList (вариант 1), но это ограничивает того, кто может реализовать интерфейс и количество классов, которые могут быть переданы в ConsumerA. Или я мог бы изменить реализацию (класс MyData), чтобы он возвращал IEnumerable вместо List (вариант 2), но затем ConsumerB пришлось бы переписать.

Кажется, это недостаток C #, если кто-то не может просветить меня.


Answers

Вы можете реализовать следующее:

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

Хм, это недостаток, я бы сказал, нет.

В любом случае, я бы работал вокруг него, как ответ дарина, или, если вы явно хотите использовать аксессуар List, вы можете сделать это следующим образом:

public class MyData : ISomeData
{

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

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

}

Подпись участника не может быть разной.

Вы все равно можете вернуть List<string> в метод get , но подпись должна быть такой же, как интерфейс.

Так что просто измените:

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

в

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

Что касается вашего другого варианта: изменение интерфейса для возврата List . Этого следует избегать. Это плохая encapsulation и считается запахом кода .


Интерфейсы требуют, чтобы подпись метода точно соответствовала сигнатуре контракта.

Вот более простой пример, который также не будет компилироваться:

interface IFoo
{
    Object GetFoo();
}

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

Теперь о том, что с этим делать, я бы позволил интерфейсу диктовать реализацию. Если вы хотите, чтобы контракт был IEnumerable<T> то это то, что должно быть в классе. Интерфейс является самым важным здесь, поскольку реализация вольна быть такой же гибкой, как и должна быть.

Просто IEnumerable<T> что IEnumerable<T> - лучший выбор здесь. (Это очень субъективно, поскольку я мало знаю о вашем домене или цели этих типов в вашем приложении. Удачи!)


Если вам нужен список в вашем интерфейсе (известное количество элементов со случайным доступом), вам следует рассмотреть возможность изменения интерфейса на

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

Это даст вам как расширяемость, так и функции, которые вам нужны из списка.

List<T> не может быть легко подклассифицирован, что означает, что у вас может возникнуть проблема с возвратом этого точного типа из всех классов, которые хотят реализовать ваш интерфейс.

ICollection<T> с другой стороны, может быть реализовано различными способами.


Для того, что вы хотите сделать, вы, вероятно, захотите реализовать интерфейс явно с членом класса (не интерфейса), который возвращает List вместо 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
}

Изменить: для пояснения это позволяет классу MyData возвращать List, когда он рассматривается как экземпляр MyData; в то же время позволяя ему возвращать экземпляр IEnumerable при обращении как экземпляр ISomeData.


К сожалению, тип возврата должен совпадать. То, что вы ищете, называется «ковариация возвращаемого типа», а C # не поддерживает это.

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

Эрик Липперт, старший разработчик команды компилятора C #, упоминает в своем блоге, что они не планируют поддерживать ковариацию возвращаемого типа.

«Такая дисперсия называется« ковариацией возвращаемого типа ». Как я упоминал ранее в этой серии, (а) эта серия не относится к такой дисперсии, и (б) мы не планируем реализовывать такую ​​дисперсию в C #. "

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

Стоит прочитать статьи Эрика о ковариации и контравариантности.

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


Я бы выбрал вариант 2:

Точка определения интерфейса в вашем коде - это определение контракта, поэтому вы и другие люди, которые реализуют ваш интерфейс, знают, что нужно согласовать. Независимо от того, задаете ли вы IEnumerable или List в своем интерфейсе, действительно проблема контракта и относится к руководству по разработке структуры. Вот целая книга, чтобы обсудить это.

Лично я бы разоблачил IEnumerable и внедрил MyData в IEnumerable, и вы можете вернуть его обратно в метод List в RandomAccess ().


Что делать, если вы получили доступ к вашему объекту MyData через интерфейс ISomeData? В этом случае IEnumerable может быть базового типа, не назначаемого List.

IEnumerable<string> iss = null;

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

РЕДАКТИРОВАТЬ:

Я понимаю, что вы имеете в виду из своих комментариев.

Во всяком случае, я бы сделал в вашем случае:

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

Преобразование в общий интерфейс с соответствующими предложениями ограничения я считаю лучшим как гибкостью, так и удобочитаемостью.


Почему бы просто не вернуть список из вашего интерфейса ...

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

Если вы знаете, что ваши потребители собираются как перебирать его (IEnumerable), так и добавлять к нему (IList), тогда кажется логичным просто вернуть список.


Я предпочитаю

Тем не менее, если вы используете, вы, вероятно, не используете наследование должным образом.

Предположим, что Person: Entity и это Animal: Entity. Feed - это виртуальный метод в Entity (чтобы сделать Neil счастливым)

class Person
{
  // A Person should be able to Feed
  // another Entity, but they way he feeds
  // each is different
  public override void Feed( Entity e )
  {
    if( e is Person )
    {
      // feed me
    }
    else if( e is Animal )
    {
      // ruff
    }
  }
}

Скорее

class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}



c#  

c#