Нельзя использовать более общий тип свойства в реализации интерфейса? [Дубликат]

Ничто, которое может сделать автор, не может выбрать для открытия в новой вкладке вместо нового окна.

CSS3 предложил target-new , но спецификация была оставлена ​​.

Обратное неверно; указав размеры для окна в третьем аргументе window.open, вы можете запустить новое окно, когда предпочтение будет указано для вкладок.

34
задан John Saunders 13 July 2009 в 19:44
поделиться

12 ответов

К сожалению, тип возврата должен совпадать. То, что вы ищете, называется «ковариация возвращаемого типа», а 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-12-to-infinity-but-not-beyond.aspx

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

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

20
ответ дан SolutionYogi 19 August 2018 в 12:17
поделиться
  • 1
    Почему это не поддерживается? Кажется логичным? – gyozo kudor 30 January 2018 в 14:57

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

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

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

instanceISomeData.Data = new SomeOtherTypeImplementingIEnumerable ();

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

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

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

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

0
ответ дан AaronLS 19 August 2018 в 12:17
поделиться

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

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

interface IFoo
{
    Object GetFoo();
}

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

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

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

1
ответ дан Andrew Hare 19 August 2018 в 12:17
поделиться

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

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

. Просто измените:

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

на

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

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

3
ответ дан Community 19 August 2018 в 12:17
поделиться
  • 1
    Делая это, вы будете ограничивать всех, кто использует класс MyData, напрямую, чтобы взаимодействовать с данными только перечислимыми способами. Это довольно ограничивает. (Если пользователь не будет возвращен в список, но это будет очень плохое решение). – Daniel 13 July 2009 в 19:50
  • 2
    Да, это ограничение, и это хорошая вещь (tm). Другие методы позволят изменять данные, если это необходимо. Возврат списка позволит другим классам управлять внутренней структурой класса MyData. Это приводит к зависти и будет плохой реализацией. Если требуется модификация структуры данных, MyData должен это сделать, никто другой. – Ben S 13 July 2009 в 20:08

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

public class MyData : ISomeData
{
    private List<string> m_MyData = new List<string>();
    public IEnumerable<string> Data { get { return m_MyData; } }
}
0
ответ дан Darin Dimitrov 19 August 2018 в 12:17
поделиться

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

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

public class MyData : ISomeData
{

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

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

}
0
ответ дан Frederik Gheysels 19 August 2018 в 12:17
поделиться

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

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

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

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

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

0
ответ дан Groo 19 August 2018 в 12:17
поделиться

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

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

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

0
ответ дан J.W. 19 August 2018 в 12:17
поделиться
  • 1
    Итак, вы говорите, что вы бы выполнили более ограничительный контракт / интерфейс, тогда вы проигнорировали бы его, перейдя в список. Это плохой дизайн, потому что вы осознаете внутреннюю работу класса. Более того, это о интерфейсе, который говорит «вы должны, по крайней мере, предоставлять данные, которые можно перечислить», и класс, который говорит «вот данные, которые вы можете перечислить и сделать произвольный доступ на AND et et». Однако компилятор говорит, что «вы не выполняете контракт!» – Daniel 13 July 2009 в 20:18
  • 2
    Нет, я НЕ предполагаю, что вы намеренно передали его конкретному списку типов, если интерфейс, который вы реализуете, является IEnumerable. Я просто предлагаю, чтобы у вас было обходное решение, если вы перейдете к опции2. – J.W. 13 July 2009 в 21:10

Что, если вы изменили свой интерфейс, чтобы расширить 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();
    }
}
0
ответ дан Jake Pearson 19 August 2018 в 12:17
поделиться

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

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

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

1
ответ дан JP Alioto 19 August 2018 в 12:17
поделиться

Что делать, если вы получили доступ к вашему объекту 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
ответ дан Kenan E. K. 19 August 2018 в 12:17
поделиться
  • 1
    Это единственный ответ, который даже отдаленно решает, почему это не является недостатком C #. – womp 13 July 2009 в 19:48
  • 2
    Ну, я считаю, что это хороший контракт. – Kenan E. K. 13 July 2009 в 19:51
  • 3
    Интерфейс определяет только свойство get для этого свойства, поэтому нет контекста, в котором вы бы предположили, что присвоение IEnumerable будет разрешено. Я вижу, что вы говорите, но поскольку я не указывал набор, ваша точка недействительна в этом примере. – Daniel 13 July 2009 в 20:11
  • 4
    @Daniel: вы можете иметь only свойство getter для списка и все еще иметь возможность добавлять / удалять элементы. С IEnumerable никто не может рассказать вам, что происходит в классе реализации. – Groo 13 July 2009 в 20:18

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

22
ответ дан STW 19 August 2018 в 12:17
поделиться
  • 1
    Yooder, спасибо за ваш ответ. Я бы не назвал это симпатичным (не твоя вина), но функционально, он доставит меня туда, куда я хочу. – Daniel 13 July 2009 в 22:37
Другие вопросы по тегам:

Похожие вопросы: