Не могу заставить co / contra-дисперсию работать в C # - PullRequest
0 голосов
/ 07 апреля 2011
abstract class A<T> {
  List<T> Items { get; set; }
}

class B {}
class C : A<B> {}

class D : B {}
class E : A<D> {}

static class X {
  public A<B> GetThing(bool f) {
    return f ? new E() : new C();
  }
}

Тип условного выражения не может быть определен, поскольку не существует неявного преобразования между «ConsoleApplication4.E» и «ConsoleApplication4.C»

Теперь я понимаю «почему» (я думаю), но я не вижу, как сделать эту компиляцию. Я думаю, что мне нужно создать интерфейс, который определяет дисперсию и наследует ее, но я не уверен. Но независимо от того, E() должно наследоваться от C().

Есть ли кто-нибудь?

1010 * ТИА *

Ответы [ 3 ]

15 голосов
/ 07 апреля 2011

Чтобы использовать универсальную дисперсию в C #, вы должны выполнить все следующие условия:

  1. Использовать C # 4
  2. Тип, который изменяется, должен быть универсальныминтерфейс или универсальный делегат
  3. Параметр типа должен быть помечен "in" (контравариантный) или "out" (ковариантный)
  4. Аннотации параметра типа должны давать тип, который доказуемо безопасен во всех своих типах.возможные операции
  5. Аргумент типа «источник» и аргумент типа «место назначения» должны иметь между собой преобразование идентификатора или ссылки.

Ваша программа соответствует условию 1 и условию 5, но неусловия 2, 3 или 4.

Невозможно получить требуемую дисперсию, потому что вы хотите что-то, что будет нарушать условие 4 .Посмотрите, что происходит, когда мы выполняем все условия, кроме условия 4:

// Suppose this declaration were legal:
interface IMyCollection<out T> 
{
  List<T> Items { get; set; } 
}

class Animal {}
class AnimalCollection : IMyCollection<Animal> 
{ 
  public List<Animal> { get; set; }
}

class Giraffe : Animal {}
class GiraffeCollection : IMyCollection<Giraffe> 
{
  public List<Giraffe> { get; set; } 
}

static class X 
{
  public IMyCollection<Animal> GetThing() 
  {
    // IMyCollection is covariant in T, so this is legal.
    return new GiraffeCollection(); 
  }
}

class Tiger : Animal {}

...

IMyCollection<Animal> animals = X.GetThing(); 
// GetThing() actually returns a GiraffeCollection
animals.Items = new List<Animal>() { new Tiger(); }

И в коллекции жирафов теперь есть список животных с тигром.

Вы должны бытьБезопасное использование типов, если вы собираетесь использовать дисперсию. Поскольку компилятор не может определить аннотации дисперсии как безопасные с точки зрения типов, он отклоняет объявление IMyCollection.

Давайте посмотрим, как мы можем это сделать и быть безопасными с точки зрения типов. Проблема в том, что Предметы доступны для записи через интерфейс.

interface IMyCollection<out T> 
{
  IEnumerable<T> Items { get; }
}

class Animal {}
class AnimalCollection : IMyCollection<Animal> 
{ 
  public IEnumerable<Animal> { get { yield return new Tiger(); } }
}

class Giraffe : Animal {}
class GiraffeCollection : IMyCollection<Giraffe> 
{
  public IEnumerable<Giraffe> { get { yield return new Giraffe(); } } 
}

static class X 
{
  public IMyCollection<Animal> GetThing() 
  {
    return new GiraffeCollection();
  }
}

class Tiger : Animal {}

...

MyCollection<Animal> animals = X.GetThing(); 
// GetThing() actually returns a GiraffeCollection
foreach(Animal animal in animals.Items) { ... } 
// Items yields a giraffe, which is an animal

Идеально безопасны.Это будет легальная программа на C # 4.

Если вас интересуют детали разработки ковариации и контравариантности в C # 4, вы можете прочитать мои дюжины статей по этому вопросу.Вы можете найти их здесь:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

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

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

http://blogs.msdn.com/b/ericlippert/archive/2009/12/03/exact-rules-for-variance-validity.aspx

3 голосов
/ 07 апреля 2011

Нет никакого отношения между E и C.Таким образом, ваш тип возвращаемого значения должен быть A<B>, который может рассматриваться как общий родительский для этих типов.Также A должен быть интерфейсом, где общий аргумент определен с out, чтобы это работало:

interface A<out T> { }

class B { }
class C : A<B> { }

class D : B { }
class E : A<D> { }

static class X
{
    public static A<B> GetThing(bool f)
    {
        if (f)
        {
            return new E();
        }
        return new C();
    }
}

Не удалось заставить его работать с троичным оператором (?:) .Компилятору просто это не нравится.


ОБНОВЛЕНИЕ:

После превосходного замечания @Eric Lippert в разделе комментариев вы можете использовать троичный оператор, приведя к A<B>:

public static A<B> GetThing(bool f)
{
    return f ? (A<B>)new E() : new C();
}
0 голосов
/ 07 апреля 2011

Это не может работать, потому что C и E не имеют общего родителя.Для этого вам необходимо создать общий родительский тип, будь то класс или интерфейс.

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