Наследование списка <T> для реализации коллекций - плохая идея?

Однажды я прочитал статью Имаара Спаанджара о том, как создать 3-х ярусные приложения. (http://imar.spaanjaars.com/416/building-layered-web-applications-with-microsoft-aspnet-20-part-1), который в течение некоторого времени стал основой моего кодирования.

Таким образом, я реализую коллекции, как он это сделал, наследуя List<T>. Поэтому, если у меня есть класс с именем Employee, для реализации коллекции у меня также будет класс Employees, как показано ниже.

class Employee
{
   int EmpID {get;set;}
   string EmpName {get;set;}  

}

class Employees : List<Employee>
{
   public Employees(){}
}

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

например. если я хочу получить подмножество от Employees, например

 Employees newEmployees = (Employees) AllEmployees.FindAll(emp => emp.JoiningDate > DateTime.Now);

Это генерирует исключение System.InvalidCastException. Однако, если я использую следующее, то нет проблемы.

List<Employee> newEmployees = AllEmployees.FindAll(emp => emp.JoiningDate > DateTime.Now);

Итак, как мне реализовать Employees, так что мне не нужно явно использовать List<Employee> в моем DAL или BLL? Или, может быть, как мне избавиться от InvalidCastexception?

Ответ 1

Я бы не наследовал от List<T> - он вводит такие проблемы, и не очень помогает (так как нет методов virtual для переопределения). Я бы использовал либо List<T> (или более абстрактный IList<T>), либо ввести полиморфизм Collection<T> имеет виртуальные методы.

В качестве примечания; например, FindAll, вы также можете найти полезные параметры LINQ (например, .Where()); в первую очередь, они будут работать для любых IList<T> (или IEnumerable<T>), а не только List<T> и подклассов.

Ответ 2

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

С точки зрения решения проблемы кастинга вы не можете сбрасывать с List<Employee> на Employees, поскольку List<Employee> не наследует от Employees. Если вам нужно вернуть Работников с вашими критериями, тогда лучше всего инкапсулировать вызов и вставить элементы возвращенного списка в свой собственный объект Employees. Это кажется пустой тратой времени, если, как я уже сказал выше, у вас есть веская причина для подкласса List<Employee>.

Лично я попытался бы использовать IList<T> там, где это было возможно, и не создавать подклассы, если у них нет причины существовать.

Ответ 3

Я бы лично сохранил список как член объекта-контейнера, и, если необходимо, получите доступ к самому списку.

class MyCollection<T> 
{
    List<T> _table;
    public List<T> Table
    {
        get
        {
            return _table;
        }
    }
    // ... other access/utility functions common for all tables 
    //     (CRUD for example)
}

class Employees : MyCollection<Employee>
{
    // ... yet more methods 
}

Я должен признать, что я сделал это, чтобы предоставить себе такие методы, как:

T FindById(int id);
void Add(T entity);
void Remove(T entity);

в базовом классе и:

T[] GetEmployeesBy(your_filter_here);

Я (все еще) использую .net 2.0, поэтому у меня нет linq - возможно, причина для этого здесь.

Ответ 4

Простое эмпирическое правило состоит в том, чтобы начать с СОСТАВА (например, обертывать сотрудников вокруг общей коллекции) и НЕ НАСЛЕДОВАТЬ. Начиная с наследования, основанного на дизайне, вы рисуете себя в углу. Состав более гибкий и модифицируемый.