Согласованность массива в общем списке C # - PullRequest
4 голосов
/ 04 января 2012

У меня есть пример, где я хочу, чтобы интерфейс абстрактного класса возвращал что-то вроде этого

abstract class AnimalProcessor {
    public abstract IList<Animal> ProcessResults();
}

Тогда конкретные примеры

class GiraffeProcessor : AnimalProcessor {
    public override IList<Animal> ProcessResults() {
        return new List<Giraffe>();
    }
}

class LionProcessor : AnimalProcessor {
    public override IList<Animal> ProcessResults() {
        return new List<Lion>();
    }
}

Проблема в том, что конкретные классы должны иметь одну и ту же сигнатуру для переопределения метода ProcessResults(), поэтому они должны возвращать IList<Animal>, однако АКТУАЛЬНЫЕ данные, которые я хочу вернуть, это IList<Lion>, IList<Giraffe> и т. д., но тогда вызывающий код должен сделать

GiraffeProcessor processor = new GiraffeProcessor();
IList<Animal> results = processor.GetResults();

Что не дает мне список, который я хочу.

Проблемы

1) Выше код не компилируется. GiraffeProcessor должен возвращать конкретный List<Animal>, вы можете заполнить его Giraffe объектами, но тип объекта, который вы создаете для возврата, должен быть List<Animal>. Не идеально.

2) Когда вы возвращаете результаты, вы можете получить только IList<Animal>, а не IList<Giraffe>. Я попытался привести в явном виде к IList<Giraffe> с IList<Giraffe> results = (IList<Giraffe>) processor.GetResults(); который выдает ошибку времени выполнения, предположительно потому, что возвращаемый объект НЕ является IList<Giraffe>, это IList<Animal>, который содержит Giraffe объектов.

Может кто-нибудь подсказать, что я делаю не так с моим дизайном, так как я немного озадачен тем, как лучше всего это сделать.

Ответы [ 3 ]

6 голосов
/ 04 января 2012

Как насчет:

abstract class AnimalProcessor<T> where T : Animal {
    public abstract IList<T> ProcessResults();
}

class GiraffeProcessor : AnimalProcessor<Giraffe> {
    public override IList<Giraffe> ProcessResults() {
        return new List<Giraffe>();
    }
}

class LionProcessor : AnimalProcessor<Lion> {
    public override IList<Lion> ProcessResults() {
        return new List<Lion>();
    }
}
1 голос
/ 04 января 2012

Если вы используете C # 4.0, вы можете спросить себя, должен ли процессор возвращать IEnumerable<T> вместо IList<T>.Если ответ «да», то вы можете получить выгоду от ковариации:

abstract class AnimalProcessor { 
    public abstract IEnumerable<Animal> ProcessResults(); 
} 

class GiraffeProcessor : AnimalProcessor { 
    public override IEnumerable<Animal> ProcessResults() { 
        return new List<Giraffe>(); 
    } 
} 

class LionProcessor : AnimalProcessor { 
    public override IEnumerable<Animal> ProcessResults() { 
        return new List<Lion>(); 
    } 
} 

У вас есть несколько преимуществ здесь.Во-первых, вы могли бы реализовать их как блоки итераторов:

class GiraffeProcessor : AnimalProcessor { 
    public override IEnumerable<Animal> ProcessResults() { 
        yield break;
    } 
} 

Во-вторых, и менее просто, вы позволяете клиентскому коду решать, в какую коллекцию помещать животных - если таковые имеются.Например, предположим, что потребитель может захотеть LinkedList<Animal>:

var animals = new LinkedList<Animal>(animalProcessor.ProcessResults());

. Или учтите, что клиенту может потребоваться только повторять последовательность:

foreach (var animal in animalProcessor.ProcessResults())
    { /*... do something ...*/ }

В любом случае, есливы использовали вызов ToList() в ProcessResults, вы бы создавали список даром.Если потребитель действительно хочет List<Animal>, это можно сделать очень легко:

var animals = new List<Animal>(animalProcessor.ProcessResults());

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

abstract class AnimalProcessor<T> where T : Animal { 
    public abstract IEnumerable<T> ProcessResults(); 
} 

class GiraffeProcessor : AnimalProcessor<Giraffe> { 
    public override IEnumerable<Giraffe> ProcessResults() { 
        yield break;
    } 
} 

class LionProcessor : AnimalProcessor<Lion> { 
    public override IEnumerable<Lion> ProcessResults() { 
        return Enumerable.Empty<Lion>();
    } 
} 
1 голос
/ 04 января 2012

Вы можете решить эту проблему, объявив AnimalProcessor с ограничением общего типа, например,

public abstract class AnimalProcessor<T> where T : Animal 
{    
    public abstract IList<T> ProcessResults(); 
} 

Если это не сработает, вы можете использовать оператор LINQ Cast, например:

public class GiraffeProcessor : AnimalProcessor 
{     
    public override IList<Animal> ProcessResults() 
    {         
        return new List<Giraffe>().Cast<Animal>();
    } 
}

Или сохраните список внутри как Animal, но добавьте к нему Giraffe's, например.

public class GiraffeProcessor : AnimalProcessor 
{     
    private List<Giraffe> _innerList = new List<Giraffe>();
    public override IList<Animal> ProcessResults() 
    {         
        return new List<Animal>(innerList );        } 
}

С уважением,

...