#c# #entity-framework #abstract-class
#c# #entity-framework #абстрактный класс
Вопрос:
У меня очень интересная ситуация, и, похоже, я не могу найти ничего конкретного в сетях, связанных с отражением, наследованием и обобщениями.
Для начала:
У меня есть базовый абстрактный класс, называемый:
public abstract class ItemBase
{
// default properties
}
Затем ItemBase становится моим суперклассом для абстрактной сущности под названием Item:
public abstract class Item
{
// some extra properties
}
Идея, стоящая за этим, заключается в создании объектов, которые будут наследоваться от элемента с дополнительными установленными свойствами:
public partial class City : Item {}
Теперь, в тестовом методе, я пытаюсь использовать отражение, чтобы вызвать метод для переданного объекта и вернуть любые данные, в какой бы то ни было форме, обратно из метода.
Предполагая, что для целей тестирования у нас есть метод в состоянии, который возвращает коллекцию всех крупных городов:
public List<City> GetAllMajorCities() {}
В моих заглушках у меня может быть тест для получения всех городов:
List<Item> cities = this.InvokeGenericMethod<Item>("State", "GetAllMajorCities", args);
Тогда InvokeGenericMethod(строка, string, object[] аргументы) выглядит примерно так:
public IEnumerable<T> InvokeGenericMethod<Item>(string className, string methodName, object[] args)
{
Type classType = className.GetType(); // to get the class to create an instance of
object classObj = Activator.CreateInstance(classType);
object ret = classType.InvokeMember(methodName, BindingFlags.InvokeMethod, null, classObj, args);
}
Теоретически, из приведенного примера, значение «ret» должно быть типом IEnumerable, однако возвращаемый тип, если он добавлен в watch для investivage, возвращает список, если я проверяю значение ret как:
if (ret is IEnumerable<T>)
проверка игнорируется.
Если я изменю
public List<City> GetAllMajorCities() {}
Для
public List<ItemBase> GetAllMajorCities() {}
Кажется, он придерживается наследования и позволяет мне преобразовать его в IEnumberable.
Я здесь в полном тупике.
Любые идеи, рекомендации были бы высоко оценены.
Спасибо,
Эрик
Комментарии:
1. Не могли бы вы, пожалуйста, перепроверить свой код? Я думаю, что там что-то перепуталось. Я предполагаю, что ваш InvokeGenericMethod не будет работать. Имя_класса. GetType() всегда возвращает тип string, например.
2. Привет, Ахим, приведенный выше код является примером реальных извинений, если он немного сбивает с толку. Я использую отражение полного имени класса, пространства имен класса в строковом формате при привязке выпадающего списка, который затем передается в виде строки в мой InvokeGenericMethod.
Ответ №1:
Возможно ли, что вы используете Framework 3.5? Общая со- и контравариантность была введена в .net 4.0. Таким образом, следующее невозможно в Framework 3.5:
class ItemBase { }
class Item : ItemBase { }
class City : Item { }
class Program
{
static void Main(string[] args)
{
IEnumerable<City> cities = new List<City>();
IEnumerable<Item> items = cities; // Compile error here. IEnumerable<Item> is not a supertype of IEnumerable<City>.
}
}
Обратите внимание, что следующее (чего вы пытаетесь достичь) не будет работать даже в .net 4.0:
List<Item> cities = new List<City>();
Почему это не работает? Потому что должна быть возможность добавлять произвольные Item
s к List<Item>
. Но, очевидно, cities.Add(new Person());
не может работать (поскольку «реальный» (= статический) тип cities
является List<City>
). Таким образом, List<Item>
никогда не может быть супертипом List<City>
.
Вы не можете добавлять элементы в IEnumerable, вот почему между и IEnumerable<City>
существует IEnumerable<Item>
взаимосвязь подтипов (но только в .net 4.0, как упоминалось выше).
Комментарии:
1. Привет, Хайнци. Спасибо за объяснение, которое имеет смысл. Ложное понимание, которое я получил, заключалось в том, что у нас есть суперкласс ItemBase, что подклассы автоматически будут определены как таковые при отражении. Небольшая оплошность с моей стороны.
Ответ №2:
Вы могли бы попробовать что-то вроде этого:
Type type = ret.GetType();
Type listType = typeof(IEnumerable<T>);
if (type == listType || type.IsSubclassOf(listType))
{
//
}
Ответ №3:
Прежде всего, это неправильно:
Type classType = className.GetType();
Эта конструкция выдаст тип, className
которым является String
, но не тип, имя которого хранится в className
.
Лучшим, более типобезопасным способом было бы:
public IEnumerable<TItem> InvokeGenericMethod<TItem, TContainer>(...)
where TItem : ItemBase
where TContainer : class, new()
{
TContainer container = new TContainer();
...
}
Обновление: В случае, если TContainer
тип не известен статически в том месте, где InvokeGenericMethod
вызывается, взгляните на статические Type.GetType
методы для преобразования фактического имени из его строкового имени. В простейшем случае Type.GetType(String)
метод обращается к определенному имени сборки вашего типа, например "MyNamespace.City, MyAssembly"
.
Комментарии:
1. Привет, Ондрей, твое предложение сработает в ситуации, когда фактический класс известен в его «чистой» форме, однако у меня в элементе управления есть выпадающий список, в котором перечислены имена объектов (классов) в виде строкового значения. Мне нужно получить базовый тип связанной строки из сборки. Я изучу это подробнее, поскольку чувствую, что предложенное вами решение может иметь смысл, если я изменю свою логику.
2. @JadedEric соответственно понял и обновил мой ответ. Однако, если вы можете изменить свою логику в сторону более типобезопасного решения, возможно, вообще избегая рефлексии, вы, как правило, выиграете от этого в будущем.