Создайте экземпляр типа, используя базовый класс с дженериками

В своем коде я создал систему плагинов, которая загружает типы из DLLS. Я беру нужный мне тип из загруженной DLL, используя этот код;

var type = Assembly.LoadFrom(filePath).GetTypes()
                    .FirstOrDefault(t =>
                        t.IsClass && t.IsSubclassOfRawGeneric(typeof(DespatchBasePlugin<>)));

IsSubClassOfRawGeneric находит базовый тип, так как он скрыт несколькими классами, код работает и возвращается правильный тип.

Затем я создаю экземпляр этого класса с помощью Activator;

DespatchBasePlugin<XMLSettingBase> obj = Activator.CreateInstance(type, new object[] { logger }) as DespatchBasePlugin<XMLSettingBase>;

К сожалению, приведение в этой строке создает нулевую ссылку. Удаление приведения возвращает экземпляр рассматриваемого класса, но мне нужно сохранить его как базовый тип.

Это загружаемый класс (сокращенно для краткости);

public class DHLPlugin : DespatchBasePlugin<UserSetting>
{
    public DHLPlugin(BaseForm logger) : base("DHL", logger)
    {
        this.order = 10;
    }
}

И это базовый класс, который я хочу использовать (обратите внимание, что сам класс имеет базовый класс, он идет на несколько уровней в глубину);

public abstract class DespatchBasePlugin<TSettings> : DespatchBase<TSettings> where TSettings : XMLSettingBase, new()

Предыдущий код использовал базовый класс без назначенного ему универсального и работал абсолютно нормально. Это выглядело так;

DespatchBasePlugin obj = Activator.CreateInstance(type, new object[] { logger }) as DespatchBasePlugin;

Я уверен, что делаю что-то глупое, пожалуйста, скажите мне, что это такое.

Изменить. Не помечен как дубликат, так как я считаю, что это лучший вопрос/ответ, чем другой, который состоит из общей ссылки на MSDN в качестве ответа. Если это неправильный способ использования системы дублирования, сообщите мне об этом.


person Tony Cheetham    schedule 02.02.2018    source источник
comment
Можете ли вы показать нам объявление класса для класса, который он создает, а также определение DespatchBasePlugin<>?   -  person Llama    schedule 02.02.2018
comment
Обновлено, спасибо, Джон. Я пропустил базовые классы базового класса, он идет примерно на 4 уровня в глубину, не уверен, нужны ли они вам все?   -  person Tony Cheetham    schedule 02.02.2018
comment
Я думаю, этого достаточно. Позвольте мне найти правильный пост SO, чтобы помочь вам.   -  person Llama    schedule 02.02.2018
comment
Возможный дубликат сбой приведения к универсальному базовому классу   -  person Llama    schedule 02.02.2018
comment
Невозможно изменить типизированный параметр таким образом.   -  person fdafadf    schedule 02.02.2018
comment
То, что ты делаешь, кажется мне странным. Я ожидаю, что система плагинов имеет четко определенный IPlugin, который должен реализовать каждый класс. Идя таким путем, у вас не будет проблем, которые вы описываете, потому что вам не нужно что-то разыгрывать.   -  person user743414    schedule 02.02.2018
comment
Расскажи мне об этом @user743414, это не я придумал ;) Просто пытаюсь разобраться, не переписывая все это. Похоже, я мог бы использовать это, используя оболочку интерфейса.   -  person Tony Cheetham    schedule 02.02.2018
comment
@tonyenkiducx Вот старая статья из codeproject codeproject. com/Articles/1052356/ возможно, это поможет. Я не пишу все снова :) Кстати. то, что написал Джон, похоже.   -  person user743414    schedule 02.02.2018
comment
@ user743414 Спасибо! Решение для существующего проекта выглядит просто как добавление интерфейса на верхнем уровне, обновление моих ссылок на плагины для использования интерфейса, а затем добавление в интерфейс всех элементов, которые вызываются в основном приложении. Изменение 1 типа из 9 породило 1500 ошибок в VS, прежде чем закончилось место, но это прогресс :)   -  person Tony Cheetham    schedule 02.02.2018


Ответы (1)


Вы можете использовать контравариантность для определения вашего плагина:

public class Program
{
    public static void Main()
    {
        var settings = new DerivedSettings()
        {Name = "John"};
        DerivedPlugin a = new DerivedPlugin(settings);
        IPlugin<BaseSettings> sample = (IPlugin<BaseSettings>)a;
        Console.WriteLine(sample.GetName());
    }
}

public abstract class BaseSettings
{
    public abstract string Name
    {
        get;
        set;
    }
}

public interface IPlugin<out TSettings>
    where TSettings : BaseSettings
{
    string GetName();
}

public abstract class BasePlugin<TSettings> : IPlugin<TSettings> where TSettings : BaseSettings
{
    protected readonly TSettings _settings;
    public BasePlugin(TSettings settings)
    {
        _settings = settings;
    }

    public virtual string GetName()
    {
        return _settings.Name;
    }
}

public class DerivedSettings : BaseSettings
{
    public override string Name
    {
        get;
        set;
    }
}

public class DerivedPlugin : BasePlugin<DerivedSettings>
{
    public DerivedPlugin(DerivedSettings settings): base (settings)
    {
    }
}

Я включил класс BasePlugin, но это необязательно, и вы можете просто напрямую использовать интерфейс.

person Llama    schedule 02.02.2018
comment
Я понимаю, что это ужасно, но я думаю, что мне придется добавить оболочку интерфейса, а затем мне придется приводить плагин к типу BasePlugin, когда я его использую. Это устаревший код... 54 проекта в решении... Невозможно переписать все это. - person Tony Cheetham; 02.02.2018
comment
Пометка как ответ. Не помечая его как дубликат, потому что я считаю, что этот вопрос и ответ лучше, чем другой. Ваш ответ гораздо более явный и показывает решение, а не просто указывает на статью MSDN. - person Tony Cheetham; 02.02.2018