Почему понятия «ковариантность» и «контравариантность» применимы при реализации методов интерфейса? - PullRequest
7 голосов
/ 23 марта 2010

Пример использования примерно такой:

public class SomeClass : ICloneable
{
    // Some Code

    // Implementing interface method
    public object Clone()
    {
        // Some Clonning Code
    }
}

Теперь мой вопрос Почему невозможно использовать «SomeClass (как он получен из объекта)» в качестве возвращаемого типаметода Clone (), если мы рассмотрим основы ковариации и контравариантности ?

Может кто-нибудь объяснить мне причину этой реализации Microsoft ????

Ответы [ 6 ]

8 голосов
/ 26 марта 2010

Позвольте мне перефразировать вопрос:

Такие языки, как C ++, позволяют переопределенному методу иметь более конкретный тип возврата, чем переопределенный метод. Например, если у нас есть типы

abstract class Enclosure {}
class Aquarium : Enclosure {}
abstract class Animal 
{
    public virtual Enclosure GetEnclosure();
}

тогда это недопустимо в C #, но эквивалентный код будет допустимым в C ++:

class Fish : Animal
{
    public override Aquarium GetEnclosure() { ... 

Как называется эта особенность C ++?

Функция называется «ковариация возвращаемого типа». (Как указывает другой ответ, было бы также возможно поддерживать «формальную контрастность типа параметра», а в C ++ - нет.)

Почему это не поддерживается в C #?

Как я уже неоднократно указывал, нам не нужно указывать причину, по которой функция не поддерживается; состояние по умолчанию для всех функций «не поддерживается». Только когда огромное количество времени и усилий затрачивается на реализацию, функция становится поддерживаемой. Скорее, у функций, которые реализованы , должны быть причины для них, и чертовски веские причины, учитывая, сколько стоит их сделать.

Тем не менее, есть две большие "точки против" этой функции, которые являются основными вещами, препятствующими ее выполнению.

  1. CLR не поддерживает его. Чтобы сделать эту работу, мы должны были бы реализовать метод точного соответствия, а затем создать вспомогательный метод, который его вызывает. Это выполнимо, но становится грязным.

  2. Андерс считает, что это не очень хорошая языковая функция. Андерс является главным архитектором, и если он считает, что это плохая черта, шансы хороши, она не будет выполнена. (Теперь, заметьте, мы думали, что именованные и необязательные параметры тоже не стоят затрат, но это в конечном итоге было сделано. Иногда становится ясно, что вам нужно стиснуть зубы и реализовать функцию, которая вам не очень нравится эстетика для того, чтобы удовлетворить реальный спрос.)

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

5 голосов
/ 23 марта 2010

Непрерывная реализация дисперсии реализации интерфейса должна быть ковариантной в типе возврата и контравариантной в типах аргументов .

Например:

public interface IFoo
{
    object Flurp(Array array);
}

public class GoodFoo : IFoo
{
    public int Flurp(Array array) { ... }
}

public class NiceFoo : IFoo
{
    public object Flurp(IEnumerable enumerable) { ... }
}

Оба законны по "новым" правилам, верно? Но что по этому поводу:

public class QuestionableFoo : IFoo
{
    public double Flurp(Array array) { ... }
    public object Flurp(IEnumerable enumerable) { ... }
}

Трудно сказать, какая неявная реализация лучше здесь. Первый - точное соответствие для типа аргумента, но не тип возвращаемого значения. Второе - точное совпадение для возвращаемого типа, но не типа аргумента. Я склоняюсь к первому, потому что тот, кто использует интерфейс IFoo, может только дать ему Array, но это все еще не совсем ясно.

И это далеко не худшее, безусловно. Что если мы сделаем это вместо этого:

public class EvilFoo : IFoo
{
    public object Flurp(ICollection collection) { ... }
    public object Flurp(ICloneable cloneable) { ... }
}

Кто из них выиграет приз? Это вполне допустимая перегрузка, но ICollection и ICloneable не имеют никакого отношения друг к другу, а Array реализует их обоих. Я не вижу здесь очевидного решения.

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

public interface ISuck
{
    Stream Munge(ArrayList list);
    Stream Munge(Hashtable ht);
    string Munge(NameValueCollection nvc);
    object Munge(IEnumerable enumerable);
}

public class HateHateHate : ISuck
{
    public FileStream Munge(ICollection collection);
    public NetworkStream Munge(IEnumerable enumerable);
    public MemoryStream Munge(Hashtable ht);
    public Stream Munge(ICloneable cloneable);
    public object Munge(object o);
    public Stream Munge(IDictionary dic);
}

Удачи, пытаясь разгадать эту тайну, не сходя с ума.

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

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

2 голосов
/ 23 марта 2010

Вы должны реализовать методы интерфейса в точности так, как они есть в интерфейсе. Метод Clone ICloneable возвращает объект, поэтому ваш SomeClass также должен возвращать объект. Однако вы можете без проблем вернуть экземпляр SomeClass в методе Clone SomeClass, но определение метода должно соответствовать интерфейсу:

public class SomeClass: IClonable
 {
     // Some Code

     //Implementing interface method
     Public object Clone()
      {
        SomeClass ret = new SomeClass();
        // copy date from this instance to ret
        return ret;
      }
 }
1 голос
/ 23 марта 2010

С точки зрения объяснения причин решений C # Эрик Липперт из Microsoft написал много объяснений Contra / CoVariance в C # ... вот список тегов из его блога: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

[Изменить] В зависимости от вашего вопроса, это может быть правильный пост .. http://blogs.msdn.com/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx

0 голосов
/ 23 марта 2010

Похоже, они могли использовать дженерики, но, похоже, есть веская причина, по которой они этого не сделали.

Здесь говорится:

http://bytes.com/topic/c-sharp/answers/469671-generic-icloneable

По сути, универсальный интерфейс, который позволит: public class MyClass : IClonable<MyClass>

также позволит: public class MyClass : IClonable<MyOtherClass>

, который на самом деле не приносит никакой пользы и может привести к путанице.

0 голосов
/ 23 марта 2010

В соответствии со спецификацией C # вы должны использовать метод с идентичной подписью при переопределении или реализации метода интерфейса. Имейте в виду, что Microsoft не владеет C #. Их компилятор C # - просто их реализация этого. Так почему же спецификации так поступают? Я могу только догадываться, но я подозреваю, что это было для простоты реализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...