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

186
01 октября 2018, 21:40

Подскажите, пожалуйста, у меня есть два класса; (Star,Circle) Есть ли возможность передать данный класс в метод с вызовом его конструктора.

Сейчас использую дженереки, но конструктор не работает

Пример:

public static T getObj<T>( 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<Star>(3, i)

Ошибка: CS0417 'T": при создании экземпляра типа переменной не удается задать аргументы

как верно передать класс в метод заместо "new T"?

Answer 1

То, в каком виде пытаетесь это сделать Вы, сделать не получится.
Вы не можете наложить ограничение на 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<T>(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<T>(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<T>(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<T>(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<T>(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<T>();

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

READ ALSO
C#, WinForms Settings

C#, WinForms Settings

У меня возник вопрос, как же можно сделать пользовательскую настройку для приложенияУ меня существует несколько форм

187
База данных на simpleXML в PHP

База данных на simpleXML в PHP

Пишу код для создания базы данных в xml файле

200
Построение селекта ACF

Построение селекта ACF

ЗдравтсвуйтеЗадача: Построить мультиселект с опцией добавления к нему значений юзером

175