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



5 Answers

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

Question

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

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 #, если кто-то не может просветить меня.




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

Вы все равно можете вернуть 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 class MyData : ISomeData
{
    private List<string> m_MyData = new List<string>();
    public IEnumerable<string> Data { get { return m_MyData; } }
}



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

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

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




Я столкнулся с подобными ситуациями и хочу дать более конкретный пример того, почему это недопустимо. Общие части моего приложения связаны с интерфейсом, который содержит свойства, и предоставляет данные через этот интерфейс в другую часть приложения, содержащую конкретные классы, реализующие эти интерфейсы.

Цель, которая, как мне кажется, похожа на вашу: классы-классы имеют больше функциональности, а интерфейс обеспечивает абсолютный минимум, необходимый для общей части приложения. Для интерфейса очень полезно выявить наименьшее количество функциональных возможностей, поскольку оно максимизирует совместимость / повторное использование. Т.е. это не требует, чтобы разработчик интерфейса реализовал больше, чем это необходимо.

Однако подумайте, есть ли у вас сеттер в этом свойстве. Вы создаете экземпляр вашего конкретного класса и передаете его некоторому универсальному помощнику, который принимает ISomeData. В коде этого помощника они работают с ISomeData. Без какого-либо типа T, где T: new () или заводской шаблон, они не могут создавать новые экземпляры для ввода данных, которые соответствуют вашей конкретной реализации. Они просто возвращают список, который реализует IEnumerable:

instanceISomeData.Data = new SomeOtherTypeImplementingIEnumerable ();

Если SomeOtherTypeImplementingIEnumerable не наследует List, но вы реализовали .Data как список, то это недопустимое присвоение. Если компилятор разрешил вам это сделать, тогда сценарии, подобные этому, будут повреждены во время выполнения, потому что SomeOtherTypeImplementingIEnumerable не может быть передан в List. Однако в контексте этого помощника, работающего с ISomeData, он все равно не нарушал интерфейс ISomeData, и назначение любого типа, поддерживающего IEnumerable, должно быть действительным. Таким образом, ваша реализация, если бы это разрешила компилятор, могла нарушить превосходный код, работающий с интерфейсом.

Вот почему вы не можете реализовать .Data как производный тип. Вы более жестко ограничиваете реализацию .Data только за исключением List, а не любого IEnumerable. Таким образом, хотя интерфейс говорит, что «любой IEnumerable разрешен», ваша конкретная реализация заставит ранее существовавший код, поддерживающий ISomeData, внезапно сломаться при работе с вашей реализацией интерфейса.

Конечно, вы не сталкиваетесь с этим сценарием только с помощью геттера, но это усложнит ситуацию, чтобы позволить ему в сценариях получения, но не иначе.

Обычно я иду с решением Джейка или с решением Алиото, в зависимости от того, насколько я себя чувствую в настоящий момент.




Related



Tags

c# c#