Здравствуйте, Quebecois, Вы писали:
Q>По-моему, мы о разных вещах говорим. .Net позволяет UserAssembly инстанциировать класс из ChildAssembly, унаследованный от ParentAssembly, без ссылки UserAssembly->ParentAssembly.
Нет, об одном и том же.
Ссылка на ParentAssembly тут ни при чём.
Q>Точно о разных вещах. ParentAssembly позволяет получить такую диаграмму зависимостей (references из метаданных):
Q>UserAssembly -> ChildAssembly
Q>ChildAssembly -> ParentAssembly
Q>Еще раз, прямой ссылки из UserAssembly на ParentAssembly нет, но это не мешает создавать класс, пронаследованный оттуда через ChildAssembly.
И это — совершенно неважно.
Q>Реалистичный пример: ParentAssembly — внутренняя сборка продукта с внутренним функционалом. ChildAssembly — сборка с интерфейсами для плагинов. UserAssembly — плагин, не имеющий понятия о ParentAssembly.
Q>Если разработчик продукта не хочет ломать обратную совместимость с UserAssembly, ему достаточно придерживаться простого правила: не менять/удалять ничего из ChildAssembly. Добавлять новые классы и интерфейсы — да. Менять/удалять — нет. Это легко соблюдать и легко проверять в процессе ревью. При этом, код внутри приватной ParentAssembly можно кромсать, как хочется — плагины на нее не ссылаются и изменения их не затронут.
Нет, это не так легко соблюдать в процессе ревью, как вам кажется.
Q>Если мы добавляем сюда наследование конструкторов — получается, что поменяв конструктор в приватной ParentAssembly, мы неявно поменяли публичный интерфейс ChildAssembly, и сломали плагин. Ревью это не отловит, потому что исходники ChildAssembly мы не меняли.
Наследование конструкторов ничего не меняет в сути проблемы. Повторю, на всякий случай, основное утверждение:
любое изменение метаданных в зависимости требует перекомпиляции вашей сборки. В том числе и неявное.
То, что вы полагаете, что код ревью на уровне исходников ChildAssembly вас в этой ситуации спасёт — опасное заблуждение.
Q>На практике, ссылки из interface assemblies в private — это кривой паттерн, но текущая семантика его не ломает, а добавление наследования конструкторов — сломает. Или, выражаясь в Ваших терминах — это сделает нерекурсивное правило большого пальца рекурсивным.
Оно и так рекурсивно.
Смотрите:
ParentAssembly v1:
public class Parent
{
public virtual void Foo() => Console.WriteLine("foo");
}
ChildAssembly:
public class Child: Parent
{
}
UserAssembly v1:
var c = new ChildClass();
c.Foo();
ParentAssembly v2:
public class Parent
{
public void Foo(string x) => Console.WriteLine(x);
}
При запуске UserAssembly с ParentAssembly v2 вы получите MethodNotFound. Независимо от того, перекомпилируете ли вы ChildAssembly или нет. Несмотря на то, что исходный код ChildAssembly вовсе не менялся. И это вам ещё повезёт — потому, что вы, по крайней мере, заметите неладное.
Я могу вам накидать более тонких примеров, в которых будет вызываться не тот метод, которого вы ожидаете. Или тот метод, но с неверными аргументами.