Как создать динамический тип, производный от закрытого универсального типа

#c# #.net #types #reflection.emit

#c# #.net #типы #отражение.излучать

Вопрос:

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

Я абсолютно осознаю, что есть много подводных камней и сильное влияние на производительность, но это используется только для моего тестового кода — если быть более конкретным, для создания mocks для нескольких интерфейсов.

Итак, в принципе, у меня есть класс ( TypeBuilder — смотрите Код ниже), который способен создавать эти типы. Это довольно хорошо работает для «простых» типов. Но я хочу иметь возможность передавать ему также закрытые универсальные типы. При попытке сделать это с моей текущей реализацией, при попытке создать динамический тип (через TypeLoadException ) выдается a TypeBuilder.CreateType .

Должен ли я сообщать TypeBuilder , что новый тип является универсальным типом, хотя он является производным только от закрытых универсальных типов? Если да, то как я могу это сделать?

Вот как я это использую:

 MultiTypeBuilder multiTypeBuilder = 
    new MultiTypeBuilder(Guid.NewGuid().ToString());   

multiTypeBuilder.SetBaseType(typeof(IFoo));
multiTypeBuilder.SetBaseType(typeof(IBar<IFoo>));
multiTypeBuilder.SetBaseType(typeof(IBaz));

Type multiType = multiTypeBuilder.CreateType();
  

Это моя текущая реализация (немного упрощенная):

 private class MultiTypeBuilder
{
    private readonly TypeBuilder m_TypeBuilder;

    public MultiTypeBuilder(string name)
    {
        ModuleBuilder multiTypeModule = GetMultiTypeModule();

        TypeAttributes attributes = TypeAttributes.Interface | TypeAttributes.SpecialName | TypeAttributes.Abstract | TypeAttributes.Public;

        m_TypeBuilder = multiTypeModule.DefineType(name, attributes);
    }

    public void SetBaseType(Type baseType)
    {
        m_TypeBuilder.AddInterfaceImplementation(baseType);
    }

    public Type CreateType()
    {
        return m_TypeBuilder.CreateType();
    }

    private static ModuleBuilder GetMultiTypeModule()
    {
        AssemblyName assemblyName = new AssemblyName(c_DynamicAssemblyName);
        AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        return assemblyBuilder.DefineDynamicModule("MultiTypeAssembly");
    }
}
  

Спасибо за вашу поддержку!

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

1. В вашем примере определен ли IBar<T> таким образом, что T может быть только интерфейсом? Если нет, вы могли бы попробовать это.

2. Нет, это не так и не может быть, потому что я не знаю типы во время компиляции.

3. Компиляция вашего кода в LINQPad с пустыми интерфейсами IBar, IBaz и IFoo работает корректно, не могли бы вы опубликовать какой-нибудь код, который завершается ошибкой? Смотрите выходные данные ideone.com/is5gU

Ответ №1:

Вам нужно определить (виртуальные абстрактные) методы для интерфейсов, поскольку вы создаете абстрактный класс, а не интерфейс. Несколько ошибок:

  1. Когда вы перечисляете методы в интерфейсе, он не будет включать методы из унаследованных интерфейсов. Вам придется рекурсивно обойти унаследованные интерфейсы, плюс интерфейсы, которые они наследуют, и так далее, Чтобы найти все методы, которые вам нужно определить.

  2. При обходе интерфейсов вам нужно будет обработать случай, когда два интерфейса наследуются от одного и того же интерфейса, чтобы не пытаться создать метод дважды.

  3. В случае, если у вас есть два интерфейса, содержащих метод с одинаковой сигнатурой, вам нужно будет создать явные реализации. Они не могут быть абстрактно-виртуальными (по крайней мере, если вы хотите следовать соглашениям C #), поэтому вам нужно будет создать некоторый IL-код для пересылки к какому-либо другому виртуальному абстрактному методу.


На самом деле, я только что заметил, что вы на самом деле не указали, что это класс в атрибутах типа. Вам необходимо включить атрибуты TypeAttributes.Class или TypeAttributes.Интерфейс. В последнем случае вам не нужно выполнять всю вышеуказанную работу (и вам не понадобится Abstract, если я правильно помню.)

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

1. Спасибо за ответ, Дэн, но для простоты все типы, а также возвращаемый динамический тип являются интерфейсами. Я соответствующим образом обновил атрибуты TypeAttributes в моем вопросе. Но проблема по-прежнему остается той же.

2. @Florian, есть ли у какого-либо из интерфейсов внутренняя видимость? Если это так, вам нужно будет добавить [assembly: InternalsVisibleTo(/*c_DynamicAssemblyName*/)] в PropertyInfo.cs сборку, содержащую внутренние интерфейсы.

3. Да, я уже добавил атрибут. И это также происходит при использовании открытых интерфейсов…

Ответ №2:

Как мне не стыдно.

Как заявил VirtualBlackFox, мой слегка сжатый пример работает. В моем «реальном» коде я создаю имя динамического типа путем объединения FullNames типов, которые необходимо объединить. Это, очевидно, приводит к недопустимому имени типа при использовании с универсальными типами (я обнаружил, что, например, [ недопустимо, что имеет смысл, если вы подумаете об этом).

Если я удалю любые небуквенные или цифровые символы из сгенерированного typename, все работает как по маслу.

Спасибо за ответы / комментарии!