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