Здравствуйте, Quebecois, Вы писали: Q>Работает.
Я неудачно выразился. Этот способ может работать. А может и не сработать, причём большим количеством разнообразных способов.
Например, может вылететь исключение с method not found. А может молча выбраться не тот метод, который ожидает пользователь.
Ограничения, при которых можно безопасно использовать из сборки A сборку B после внесения в метаданные B изменений без перекомпиляции A, перечислить довольно сложно.
И изменение сигнатуры конструктора в них явно не входит.
Именно отсюда правило большого пальца: всегда перекомпилируем проект при изменении в любой из зависимостей.
Вот в вашем примере совершенно не нужно привлекать какую-то ParentAssembly.
Достаточно просто иметь ChildAssembly с классом ChildClass, отнаследованным напрямую от object.
Теперь мы заменяем сигнатуру конструктора в ChildClass c ChildClass(int x) на ChildClass(string x).
Совершенно неважно, сгенерирован ли этот конструктор автоматически языком или вручную пользователем. Результат будет одинаковым — MethodNotFound.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Дотнет не рассчитан на такое использование. Правило большого пальца: если изменилась хотя бы одна из зависимостей, проект нужно пересобирать. S>Исключения из этого правила очень редки, и требуют специальных усилий. Ну, там, к примеру, если вы пишете приложение, которое должно уметь работать с плагинами — в некотором смысле, плагины являются для вас зависимостями; но там вы будете прилагать специальные меры как со стороны приложения, так и со стороны плагинов (в виде набора ограничений на типы, экспортируемые из плагина) для предотвращения проблем с версионированием метаданных.
Здравствуйте, Sinclair, Вы писали:
S>Ограничения, при которых можно безопасно использовать из сборки A сборку B после внесения в метаданные B изменений без перекомпиляции A, перечислить довольно сложно. S>И изменение сигнатуры конструктора в них явно не входит. S>Именно отсюда правило большого пальца: всегда перекомпилируем проект при изменении в любой из зависимостей.
По-моему, мы о разных вещах говорим. .Net позволяет UserAssembly инстанциировать класс из ChildAssembly, унаследованный от ParentAssembly, без ссылки UserAssembly->ParentAssembly.
S>Вот в вашем примере совершенно не нужно привлекать какую-то ParentAssembly. S>Достаточно просто иметь ChildAssembly с классом ChildClass, отнаследованным напрямую от object. S>Теперь мы заменяем сигнатуру конструктора в ChildClass c ChildClass(int x) на ChildClass(string x). S>Совершенно неважно, сгенерирован ли этот конструктор автоматически языком или вручную пользователем. Результат будет одинаковым — MethodNotFound.
Точно о разных вещах. ParentAssembly позволяет получить такую диаграмму зависимостей (references из метаданных):
Еще раз, прямой ссылки из UserAssembly на ParentAssembly нет, но это не мешает создавать класс, пронаследованный оттуда через ChildAssembly.
Реалистичный пример: ParentAssembly — внутренняя сборка продукта с внутренним функционалом. ChildAssembly — сборка с интерфейсами для плагинов. UserAssembly — плагин, не имеющий понятия о ParentAssembly.
Если разработчик продукта не хочет ломать обратную совместимость с UserAssembly, ему достаточно придерживаться простого правила: не менять/удалять ничего из ChildAssembly. Добавлять новые классы и интерфейсы — да. Менять/удалять — нет. Это легко соблюдать и легко проверять в процессе ревью. При этом, код внутри приватной ParentAssembly можно кромсать, как хочется — плагины на нее не ссылаются и изменения их не затронут.
Если мы добавляем сюда наследование конструкторов — получается, что поменяв конструктор в приватной ParentAssembly, мы неявно поменяли публичный интерфейс ChildAssembly, и сломали плагин. Ревью это не отловит, потому что исходники ChildAssembly мы не меняли.
На практике, ссылки из interface assemblies в private — это кривой паттерн, но текущая семантика его не ломает, а добавление наследования конструкторов — сломает. Или, выражаясь в Ваших терминах — это сделает нерекурсивное правило большого пальца рекурсивным.
Здравствуйте, 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 вовсе не менялся. И это вам ещё повезёт — потому, что вы, по крайней мере, заметите неладное.
Я могу вам накидать более тонких примеров, в которых будет вызываться не тот метод, которого вы ожидаете. Или тот метод, но с неверными аргументами.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Оно и так рекурсивно. S>Смотрите:
S>UserAssembly v1: S>
S>var c = new ChildClass();
S>c.Foo();
S>
Оно не соберется без ссылки UserAssembly -> ParentAssembly:
error CS0012: The type 'ParentClass' is defined in an assembly that is not referenced.
Вызов конструктора собирается, вызов метода — нет. Потому что на первого компилятору достаточно пройти список конструкторов класса, а для второго — все методы, включая унаследованные, чтобы выбрать правильный overload.
Что интересно, преобразование к интерфейсу тоже не собирается, хотя теоретически если child-class его реализует, то parent смотреть не надо.
Здравствуйте, Quebecois, Вы писали:
Q>Т.е. на уровне метаданных достаточно вещей, вытянутых из ChildAssembly.
Это ты какие-то странные вещи придумываешь. Нельзя использовать производный класс, не имея метаданных базового.
Q>Я не говорю, что это хорошая user practice. Это просто пример того, что сейчас работает, и сломалось бы при добавлении наследования конструкторов.
Меняешь публичный интерфейс — ломается, не меняешь — не ломается. Добавление наследования конструкторов вообще ничего бы не изменило в этом отношении. Ты высасываешь проблемы из пальца.
Ад пуст, все бесы здесь.
Re[2]: почему в C# до сих пор нет наследования конструкторов?
Здравствуйте, Codealot, Вы писали:
C>есть внятные объяснения?
Но зачем он нужен?
Наследование же нужно для диспетчеризации (выбора метода на основании типа объекта).
Пока нет объекта какой смысл в наследовании?
Более того, клиенты использующие конструкторы и использующие методы самого объекта обычно разные. Конструируют объекты обычно фабрики и DI контейнеры.
И там явно указывается объект какого типа и как сконструировать.
Ну и логически размышляя, если у объектов одинаковые api (т.к. наследование) и одинаковые конструкторы, следовательно у них одинаковые зависимости и следовательно они не отличатся! Либо реализуют паттерн стратегия.
Пока не могу себе представить где бы мне понадобилось наследование классов стратегий с наследованием конструкторов.
Вот даже гипотетически пусть есть такой код:
var someObject = new BaseClass(4, "some string");
var otherObject = new DerivedClass(4, "some string");
Как наследование конструкторов упростит мой код? Мне же все равно придется писать явно тип наследуемого класса в коде.
А что делать если я не хочу наследование констуркторов? Мне как-то в наследнике придется помечать конструкторы которые нужно "отключить"?
var someObject = new BaseClass(4, "some string");
var ottherObejct = new LoggerDecoratedObject(someObject);
я бы вот точно не хотел что бы у LoggerDecoratedObject были конструкторы из BaseClass.
Здравствуйте, Quebecois, Вы писали:
Q>Оно не соберется без ссылки UserAssembly -> ParentAssembly: Q>
error CS0012: The type 'ParentClass' is defined in an assembly that is not referenced.
Q>Вызов конструктора собирается, вызов метода — нет. Потому что на первого компилятору достаточно пройти список конструкторов класса, а для второго — все методы, включая унаследованные, чтобы выбрать правильный overload. Q>Что интересно, преобразование к интерфейсу тоже не собирается, хотя теоретически если child-class его реализует, то parent смотреть не надо.
А вы как собираетесь определять, что именно было указано в референсах?
Просто в метаданных проекта ссылки на Parent нету — там только ссылка на Child. Ссылка на Parent подтягивается инфраструктурой MSBuild неявно.
В метаданных результирующей сборки ссылка на Parent может быть, а может и не быть — в зависимости от того, во что отрезолвился символ Foo.
В какой момент и как вы предлагаете разработчику определять, что при пересборке Parent можно не пересобирать User?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: почему в C# до сих пор нет наследования конструкторов?
Здравствуйте, xpalex, Вы писали:
X>Пока нет объекта какой смысл в наследовании?
Никогда не видел такое?
public MyException()
{
}
public MyException(string? message) : base(message)
{
}
public MyException(string? message, Exception? innerException) : base(message, innerException)
{
}
...
X>Конструируют объекты обычно фабрики и DI контейнеры.
Обычно? Страшно даже смотреть на твой код.
X>Ну и логически размышляя, если у объектов одинаковые api (т.к. наследование) и одинаковые конструкторы, следовательно у них одинаковые зависимости и следовательно они не отличатся!
Л — Логика.
Ну поздравляю, ты только что доказал, что наследование вообще не нужно.
Серьезно, ты вообще не знаешь как работает наследование что ли?
Ад пуст, все бесы здесь.
Re[3]: почему в C# до сих пор нет наследования конструкторов?
Тут сразу несколько впросов появляется. Я понимаю когда в System.Exception предоставляют три варианта конструирования объекта. Но зачем их все нужно протаскивать в кастомный тип?
Для оберточного исключения, когда мы прячем сбой в кишках и экспортируем наружу только тип исключения своей библиотеки, достаточно третьей формы.
Если же вы пишите логику на исключениях, то вам будет достаточно первой или второй формы. Но вот что бы нужно было больше одной... Это какой-то карго культ — "сделать как в системной библиотеке"...
X>>Конструируют объекты обычно фабрики и DI контейнеры. C>Обычно? Страшно даже смотреть на твой код.
90% new за пределами фабрик и контейнеров это конструирование DTO. Вы используете наследование в data-классах? Я их всегда больше как tuple рассматривал, а не как полноценные объекты и типы.
X>>Ну и логически размышляя, если у объектов одинаковые api (т.к. наследование) и одинаковые конструкторы, следовательно у них одинаковые зависимости и следовательно они не отличатся!
C>Л — Логика. C>Ну поздравляю, ты только что доказал, что наследование вообще не нужно. C>Серьезно, ты вообще не знаешь как работает наследование что ли?
А оно как-то само работает? Это просто механизм для чьей-то работы с однородными объектами.
Я понимаю, когда C++-ники используют наследование для всего подряд, в том числе и для избавления от дублирования кода. Т.е. когда явного отношения 'is-a' между типами нет, но классы содержат одинаковый код.
Но там это наследие времен когда еще не было лямбд. И попытка в zero-cost абстракции.
В C# я не вижу в этом смысла. Композиция и лямбды решают подобные задачи гораздо легче и понятнее.
Re[3]: почему в C# до сих пор нет наследования конструкторов?
Видел, но редко. Обычно MyException кидается в конкретной ситуации, и содержит какие-то специфичные для конкретного типа исключения параметры. Примерно так:
public class MyException: Exception
{
public int Foo { get; init;}
public string Bar { get; init;}
public MyException(int foo, string bar): base($"Invalid operation with {bar} while foo was equal to {foo}")=> (Foo, Bar) = (foo, bar);
}
Возможность бросить MyException без инициализации Foo и Bar нафиг не упала.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: почему в C# до сих пор нет наследования конструкторов?
Здравствуйте, Sinclair, Вы писали:
S>Обычно MyException кидается в конкретной ситуации, и содержит какие-то специфичные для конкретного типа исключения параметры.
Не говори за всех.
S>Возможность бросить MyException без инициализации Foo и Bar нафиг не упала.
Никто тебе не запрещает.
Ад пуст, все бесы здесь.
Re[4]: почему в C# до сих пор нет наследования конструкторов?
Здравствуйте, xpalex, Вы писали:
X>Для оберточного исключения, когда мы прячем сбой в кишках и экспортируем наружу только тип исключения своей библиотеки, достаточно третьей формы.
Одна — это уже boilerplate. То есть, больше чем нужно.
X>90% new за пределами фабрик и контейнеров это конструирование DTO. Вы используете наследование в data-классах?
data-класс или DTO? Это не одно и то же.
X>А оно как-то само работает? Это просто механизм для чьей-то работы с однородными объектами.
Ты не ответил на вопрос. Ты сделал заявление, что "если у объектов одинаковые api (т.к. наследование) и одинаковые конструкторы, следовательно у них одинаковые зависимости и следовательно они не отличатся".
Про добавление данных и методов у классов-наследников, выходит, ты никогда не слышал?
Ад пуст, все бесы здесь.
Re: почему в C# до сих пор нет наследования конструкторов?
Здравствуйте, Sinclair, Вы писали:
S>А можно поподробнее объяснить, что имеется в виду? S>Какой сценарий использования предполагается?
Скорее всего необходимость явно переобъявлять не parameterless ктор. Дуристика, по большому счету. Но если наследованием не увлекаться, то не особо то и мешает.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[2]: почему в C# до сих пор нет наследования конструкторов?
Здравствуйте, Codealot, Вы писали:
C>Никогда не видел такое?
И, объясните уже — какую задачу вы хотите решить? В чем проблема с явным определением конструкторов, и вызовом базовых, там где необходимо?
Re[3]: почему в C# до сих пор нет наследования конструкторов?
Здравствуйте, Codealot, Вы писали:
НС>>Забей на наследование вообще, тогда и проблем с кторами не будет. C>Ага, будут другие.
Тебе за решение проблем деньги как раз и платят. Наследование в том виде, в котором оно реализовано в мейнстрим ОО языках — большой клубок проблем, и кторный бойлерплейт далеко не самая неприятная из них. Если наследование не использовать — проблем будет меньше.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[4]: почему в C# до сих пор нет наследования конструкторов?