Другие уже дали хорошие ответы о том, как генерировать код во время выполнения, поэтому я подумал, что расскажу о вашем втором абзаце. У меня есть некоторый опыт с этим и я просто хочу поделиться уроком, который я извлек из этого опыта.
По крайней мере, я мог бы определить интерфейс, который они должны были бы реализовать, тогда они предоставили бы «раздел» кода, который реализовал этот интерфейс.
У вас могут возникнуть проблемы, если вы используете interfaceбазовый тип. Если вы добавите один новый метод interfaceв будущем, все существующие клиентские классы, которые реализуют interfaceтеперь, станут абстрактными, то есть вы не сможете скомпилировать или создать экземпляр клиентского класса во время выполнения.
У меня возникла эта проблема, когда пришло время добавить новый метод примерно через 1 год после доставки старого интерфейса и после распространения большого количества «устаревших» данных, которые необходимо было поддерживать. Я закончил тем, что создал новый интерфейс, который унаследовал от старого, но этот подход усложнил загрузку и создание экземпляров клиентских классов, потому что я должен был проверить, какой интерфейс был доступен.
Одним из решений, о котором я думал в то время, было вместо этого использовать фактический класс в качестве базового типа, такого как приведенный ниже. Сам класс может быть помечен как абстрактный, но все методы должны быть пустыми виртуальными методами (не абстрактными методами). Затем клиенты могут переопределить методы, которые они хотят, и я могу добавить новые методы в базовый класс, не аннулируя существующий клиентский код.
public abstract class BaseClass
{
public virtual void Foo1() { }
public virtual bool Foo2() { return false; }
...
}
Независимо от того, применяется ли эта проблема, вы должны подумать о том, как создать версию интерфейса между вашей кодовой базой и предоставленным клиентом кодом.