Страницы

Поиск по вопросам

вторник, 13 ноября 2018 г.

Как верно передать “класс” в метод?

Подскажите, пожалуйста, у меня есть два класса; (Star,Circle) Есть ли возможность передать данный класс в метод с вызовом его конструктора.
Сейчас использую дженереки, но конструктор не работает
Пример:
public static T getObj( int size, int i) where T : new() { return _objs[i] = new T(new Point(rnd.Next(0, 800), i * 20), new Point(5 - i, 15 - i), new Size(size, size)); }
Сам вызов:
getObj(3, i)
Ошибка: CS0417 'T": при создании экземпляра типа переменной не удается задать аргументы
как верно передать класс в метод заместо "new T"?


Ответ

То, в каком виде пытаетесь это сделать Вы, сделать не получится. Вы не можете наложить ограничение на T, чтобы у него обязательно был конструктор с указанными Вами типами параметров.
where T : new()
Данное ограничение new работает следующим образом:
Оно указывает, что аргумент любого переданного типа должен иметь открытый конструктор без параметров.
Как видите, нам это не подходит. Точнее, не совсем.Давайте рассмотрим несколько вариантов решения Вашей проблемы

#0. Наследование
Раз Вы пытаетесь объединить объекты, значит, они явно наделены общей логикой. А общую логику в ООП принято выносить в абстракции. Давайте так и поступим!
Я не знаю деталей реализации Вашей программы, но, судя по названиям, Вы работаете с некими фигурами. Запишем все это дело так:
// Абстрактный родительский класс public abstract class Shape { // Свойства, которые будут у всех его потомков public Point A { get; set; } public Point B { get; set; } public Size C { get; set; }
// Публичные инициализаторы public Shape() { } // Тот инициализатор, что Вы пытаетесь использовать public Shape(Point A, Point B, Size C) { this.A = A; this.B = B; this.C = C; } }
public class Circle : Shape { // Реализовываем нужные инициализаторы public Circle() { } public Circle(Point A, Point B, Size C) : base(A, B, C) { // SOMETHING } }
public class Star : Shape { // Реализовываем нужные инициализаторы public Star() { } public Star(Point A, Point B, Size C) : base(A, B, C) { // SOMETHING } }
Теперь же давайте изменим Ваш метод:
// Добавим ограничение, что T может являться только потомком Shape public static T GetShape(Point A, Point B, Size C) where T : Shape, new() { // Используем инициализатор по умолчанию, установив все свойства вручную T obj = new T { A = A, B = B, C = C };
return obj; }
Если же Вам нужно не просто установить значения свойств и в инициализатор спрятана какая-то хитрая логика, то метод можно переписать так:
// Добавим ограничение, что T может являться только потомком Shape public static T GetShape(Point A, Point B, Size C) where T : Shape { // Получим тип T Type type = typeof(T);
// Вручную сопоставим типы if (type == typeof(Star)) // Преобразуем Star к базовому классу, а после - к T, // который, как указано в ограничении, является Shape return (T)(Shape)new Star(A, B, C); else if (type == typeof(Circle)) // Преобразуем Circle к базовому классу, а после - к T, // который, как указано в ограничении, является Shape return (T)(Shape)new Circle(A, B, C);
// Если тип не опознан - выбросим ошибку throw new NotSupportedException(); }
Или же можно использовать сопоставление шаблонов из C# 7.0+ (данный вариант представлен больше для демонстрации указанных возможностей языка, чем для использования в реальных проектах):
// Добавим ограничение, что T может являться только потомком Shape public static T GetShape(Point A, Point B, Size C) where T : Shape, new() { // Инициализатор по умолчанию // Этот метод подразумевает создание объекта дважды, // что не очень логически верно. Не делайте так) T obj = new T();
// Используем паттерны сопоставления switch (obj) { case Star star: star = new Star(A, B, C); obj = (T)(Shape)star; break; case Circle circle: circle = new Circle(A, B, C); obj = (T)(Shape)circle; break; default: // Тип не распознан throw new NotSupportedException(); }
return obj; }


#1. Явное обращение к рефлексии
То, что Вы хотите, можно реализовать и с помощью нашей дорогой и любимой рефлексии (однако сразу предупреждаю, что это медленнее вышеописанного подхода. Не очень заметно на малых выборках, но все же именно поэтому рекомендуется использовать варианты выше) От слов к делу:
public static T GetShape(Point A, Point B, Size C) { // Получим тип T Type type = typeof(T);
// Получаем открытый конструктор, в который надо передать два объекта типа Point и один типа Size ConstructorInfo constructor = type.GetConstructor(new Type[] { typeof(Point), typeof(Point), typeof(Size) }); if (constructor != null) { try { // Вызовем конструктор с указанными параметрами object result = constructor.Invoke(new object[] { A, B, C });
// Если все прошло удачно, то приведем результат к указанному типу и вернем if (result != null) return (T)result; } catch (Exception ex) { // Ошибочка вышла, обработайте ее! throw new NotSupportedException(); } }
// У нас ничего не вышло, значит, тип не соответсвует заданным условиям throw new NotSupportedException(); }
Метод можно порядком ужать. Я все расписываю, чтобы Вам было удобнее и понятнее читать)
Как ужать? Таким образом, используя Activator.CreateInstance
public static T GetShape(Point A, Point B, Size C) { try { return (T)Activator.CreateInstance(typeof(T), A, B, C); } catch (Exception ex) { // Обработайте ошибку. Ее появление означает, // что у заданного типа нет нужного конструктора throw new NotSupportedException(); } }
Данный метод делает ровно то, что и предыдущий (описанный для Вашего лучшего понимания подноготной), только встроенными возможностями
С помощью рефлексии можно реализовать метод безо всякого наследования и сопоставления типов, так что метод динамически расширяем (то есть если Вы добавите еще 10 классов, реализующих указанный конструктор, Вам не придется менять данный метод для их создания, в отличие от описанных выше с помощью наследования)

UPD:
Спасибо @PashaPash за поправку!
Итак, согласно документации
T obj = new T();
также использует рефлексию для создания объекта.То есть данное выражение эквивалентно следующему:
T obj = Activator.CreateInstance();

Надеюсь, мой ответ помог Вам разобраться в вопросе и решил Вашу проблему! Удачи в Ваших начинаниях!

Комментариев нет:

Отправить комментарий