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