Подтвердить что ты не робот

"Интерфейс не реализован" при возврате производного типа

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

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

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

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

  • Измените интерфейс, чтобы быть более конкретным, и требуйте, чтобы IList был реализован.
  • Измените мой класс (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.

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 3

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

IEnumerable<string> iss = null;

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

EDIT:

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

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

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

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

Ответ 4

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

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

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

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

к

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

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

Ответ 5

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

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

interface IFoo
{
    Object GetFoo();
}

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

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

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

Ответ 6

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

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

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

Ответ 7

Что делать, если вы изменили свой интерфейс, чтобы расширить IEnumerable, чтобы вы могли перечислять объект и редактировать данные через свойство класса.

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

Ответ 8

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

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

Ответ 9

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

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

public class MyData : ISomeData
{

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

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

}

Ответ 10

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

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

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

Ответ 11

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

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

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

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

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

Ответ 12

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

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

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

instanceISomeData.Data = new SomeOtherTypeImplementingIEnumerable();

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

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

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

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