2013-08-24 10 views
5

Я пытаюсь научиться создавать общие классы с помощью C#. Может кто-нибудь объяснить, почему я получаю ошибку компиляции при запуске этой программы.C# generics with interfaces

Я создал интерфейс IZooAnimal. Все животные зоопарка будут реализовывать этот интерфейс.

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Lion : IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

ZooCage будет держать животных одного и того же типа

public class ZooCage<T> where T : IZooAnimal 
{ 
    public IList<T> Animals { get; set; } 
} 

Класс зоопарка называемые клетки

public class Zoo 
{ 
    public IList<ZooCage<IZooAnimal>> ZooCages { get; set; } 
} 

Программа, которая использует классы

class Program 
{ 
    static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var lionCage = new ZooCage<Lion>(); 
     lionCage.Animals = new List<Lion>(); 
     lionCage.Animals.Add(lion); 

     var zebra = new Zebra(); 
     var zebraCage = new ZooCage<Zebra>(); 
     zebraCage.Animals = new List<Zebra>(); 
     zebraCage.Animals.Add(zebra); 

     var zoo = new Zoo(); 
     zoo.ZooCages = new List<ZooCage<IZooAnimal>>(); 

     zoo.ZooCages.Add(lionCage); 
    } 
} 

Когда я компиляция Я получаю follo ошибка крыла: Ошибка 2 Аргумент 1: не удается преобразовать из «ConsoleApplication2.ZooCage<ConsoleApplication2.Lion>» в «ConsoleApplication2.ZooCage<ConsoleApplication2.IZooAnimal>»

Какие изменения я должен сделать для того, чтобы моя программа работать?

+2

Подробнее о ковариации и контравариантности ... возможно, начните [здесь] (http://stackoverflow.com/q/2033912/644812)? –

ответ

3

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

var lionCage = new ZooCage<IZooAnimal>(); 
    lionCage.Animals = new List<IZooAnimal>(); 

Тогда ваш код будет работать, как ожидалось.

Исходный код не работает, так как не допускается преобразовывать конкретные типы в обобщенный тип (в качестве @ default.kramer указано covariance and contravariance).

Решение, которое я придумал следующий:

// your ZooCage is still generic 
public class ZooCage<T> 
{ 
    // but you declare on creation which type you want to contain only! 
    private Type cageType = null; 
    public ZooCage(Type iMayContain) 
    { 
     cageType = iMayContain; 
     animals = new List<T>(); 
    } 
    // check on add if the types are compatible 
    public void Add(T animal) 
    { 
     if (animal.GetType() != cageType) 
     { 
      throw new Exception("Sorry - no matching types! I may contain only " + cageType.ToString()); 
     } 
     animals.Add(animal); 
    } 
    // should be generic but not visible to outher world! 
    private IList<T> animals { get; set; } 
} 

Этот код позволяет сделать:

var lion = new Lion(); 
    var lionCage = new ZooCage<IZooAnimal>(typeof(Lion)); 
    lionCage.Add(lion); 

    var zebra = new Zebra(); 
    var zebraCage = new ZooCage<IZooAnimal>(typeof(Zebra)); 
    zebraCage.Add(zebra); 

Но он выдаст ошибку на:

zebraCage.Add(lion); 

сейчас зоопарк можно безопасно расширить.

+0

+1: Да, это определенно проблема соответствия подписи. – code4life

+2

Это предотвратит ошибку компиляции, но это победит всю цель использования дженериков. Может также не сделать вообще ZooCage вообще, если вы собираетесь это сделать. Это также не соответствует требованию «ZooCage будет содержать животных одного и того же типа», потому что все клетки позволят животным любого типа. – JLRishe

+0

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

2

Поскольку вы хотите иметь несколько клеток, но каждый тип клетки может удерживать только одно животное, ваша модель слегка выключена.

я переписал код следующим образом:

  • IZooAnimal неизменна.
  • Существует ковариантный интерфейс ICage, который принимает любые типы IZooAnimal. Это позволяет вам иметь сильно типизированную клетку для каждого типа животных.
  • Затем у меня есть Cage конкретная реализация ICage. Cage является общим, но вы можете так же легко сделать его абстрактным классом, а затем сделать реалистичные реализации клеток. Например, если вашей зебре нужно кормить траву, а вашему льву нужно кормить мясо, вы можете специализироваться на реализации их клеток.

Вот полный код:

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public interface ICage<out T> where T : IZooAnimal 
{ 
    IReadOnlyCollection<T> Animals { get; } 
} 

public class Cage<T> : ICage<T> where T: IZooAnimal 
{ 
    private readonly List<T> animals = new List<T>(); 

    public IReadOnlyCollection<T> Animals 
    { 
     get 
     { 
      return animals.AsReadOnly(); 
     } 
    } 

    public void CageAnimal(T animal) 
    { 
     animals.Add(animal); 
    } 
} 

public class Lion : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zoo 
{ 
    public IList<ICage<IZooAnimal>> Cages { get; set; } 
} 

internal class Program 
{ 

    private static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var zebra = new Zebra(); 
     var lionCage = new Cage<Lion>(); 
     lionCage.CageAnimal(lion); 

     var zebraCage = new Cage<Zebra>(); 
     zebraCage.CageAnimal(zebra); 

     var zoo = new Zoo(); 
     zoo.Cages.Add(lionCage); 
     zoo.Cages.Add(zebraCage); 

    } 
} 
3

@ ответ DanielMann является довольно хорошо, но страдает от одного недостатка: оригинальный IList интерфейс не может быть использован с интерфейсом ICage. Вместо этого, ICage должен выставить ReadOnlyCollection и выставить новый метод CageAnimal.

Я также переписал код с использованием аналогичного подхода. Моя реализация ICage намного слабее, но она позволяет вам придерживаться семантики IList.

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Lion : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public interface ICage 
{ 
    IEnumerable<IZooAnimal> WeaklyTypedAnimals { get; } 
} 

public class Cage<T> : ICage where T : IZooAnimal 
{ 
    public IList<T> Animals { get; set; } 

    public IEnumerable<IZooAnimal> WeaklyTypedAnimals 
    { 
     get { return (IEnumerable<IZooAnimal>) Animals; } 
    } 
} 

public class Zoo 
{ 
    public IList<ICage> ZooCages { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var lionCage = new Cage<Lion>(); 
     lionCage.Animals = new List<Lion>(); 
     lionCage.Animals.Add(lion); 

     var zebra = new Zebra(); 
     var zebraCage = new Cage<Zebra>(); 
     zebraCage.Animals = new List<Zebra>(); 
     zebraCage.Animals.Add(zebra); 

     var zoo = new Zoo(); 
     zoo.ZooCages = new List<ICage>(); 

     zoo.ZooCages.Add(lionCage); 
    } 
} 
+0

Отличная точка. Я рассмотрел ваш подход! :) –

+0

Есть только так много способов кожи льва. :) – CSJ

+0

Действительно - гораздо более изящное и чистое решение, чем мое! :) – pasty