почему в C# до сих пор нет наследования конструкторов?
От: Codealot Земля  
Дата: 25.11.22 02:54
Оценка:
есть внятные объяснения?
Ад пуст, все бесы здесь.
Re: есть
От: Quebecois Канада https://www.canada.ca/
Дата: 25.11.22 03:47
Оценка: +5
Здравствуйте, Codealot, Вы писали:

C>есть внятные объяснения?

Явное наследование через base() есть.

Если сделать неявное, получается борода:

1. Возьмем 3 сборки:
2. Меняем в ParentAssembly int x -> string x.
3. Пересобираем ParentAssembly и ChildAssembly. Все собралось без ошибок и зарелизилось.
4. Где-то в другой временной зоне громко ругается разработчик UserAssembly, который начал вываливаться с MethodNotFoundException.

Явное определение конструкторов (которое на раз генерируется IDE) это предотвращает — ChildAssembly выдаст ошибку на этапе компиляции, разработчик вспомнит, что это public API и откатит все взад.

Если очень надо и часто меняются параметры — сделай wrapper class, как с EventArgs.
Re[2]: есть
От: Jack128  
Дата: 25.11.22 07:47
Оценка: +1
Здравствуйте, Quebecois, Вы писали:

Q>1. Возьмем 3 сборки:

Q> Q>2. Меняем в ParentAssembly int x -> string x.
Q>3. Пересобираем ParentAssembly и ChildAssembly. Все собралось без ошибок и зарелизилось.
Q>4. Где-то в другой временной зоне громко ругается разработчик UserAssembly, который начал вываливаться с MethodNotFoundException.

Q>Явное определение конструкторов (которое на раз генерируется IDE) это предотвращает — ChildAssembly выдаст ошибку на этапе компиляции, разработчик вспомнит, что это public API и откатит все взад.

Ну дык ctor ParentClass тоже public api, но разработчик спокойно его поменял.
Re: почему в C# до сих пор нет наследования конструкторов?
От: Baiker  
Дата: 25.11.22 09:37
Оценка:
Здравствуйте, Codealot, Вы писали:

C>есть внятные объяснения?


Quebecois прекрасно всё объяснил, но скорее с практической точки зрения. Public API не должен меняться вообще, если так по-хорошему.

В теории, смысл такой:

{{ базовый класс
констр 1
констр 2
}}

{{ потомок
констр 3
}}

Когда ты создаёшь класс-потомок, ты наследуешь поведение базы. НО(!) никто не сказал, что базовые конструкторы вообще подходят под конструирование потомка! Если ты ввёл "единственно правильный" констр 3, то юзер класса "потомок" вообще не должен никогда видеть констр 1! Что, собственно, язык и делает. Хочешь "пробросить" конструктор — не вопрос, просто создай его руками.

Что в C# налажали, так это вызов самих конструкторов. Если я правильно ошибаюсь, в Delphi можно вызывать базовый конструктор из любого места текущего конструктора. В C# это возможно только в начале. А может это я с D перепутал.
Re[2]: есть
От: karbofos42 Россия  
Дата: 25.11.22 11:03
Оценка:
Здравствуйте, Quebecois, Вы писали:

Q>Явное наследование через base() есть.


Если много конструкторов, то замучаешься их вот так дублировать в каждом наследнике и потом поддерживать.
В С++ есть вот такая штука:
https://learn.microsoft.com/en-us/cpp/cpp/constructors-cpp?view=msvc-170#inheriting_constructors
class Derived : Base
{
public:
    // Inherit all constructors from Base
    using Base::Base;
...
Re[2]: почему в C# до сих пор нет наследования конструкторов?
От: νsb Казахстан  
Дата: 25.11.22 11:07
Оценка: +1
Здравствуйте, Baiker, Вы писали:

B>Что в C# налажали, так это вызов самих конструкторов. Если я правильно ошибаюсь, в Delphi можно вызывать базовый конструктор из любого места текущего конструктора. В C# это возможно только в начале. А может это я с D перепутал.


В жаве та же фигня. Капец бесит.

Так можно
MyCtor() {
  super(something());
}


А так нельзя
MyCtor() {
  int s = something();
  super(s);
}


Лавров.jpg
Re[2]: почему в C# до сих пор нет наследования конструкторов?
От: karbofos42 Россия  
Дата: 25.11.22 12:24
Оценка:
Здравствуйте, Baiker, Вы писали:

B>Что в C# налажали, так это вызов самих конструкторов. Если я правильно ошибаюсь, в Delphi можно вызывать базовый конструктор из любого места текущего конструктора. В C# это возможно только в начале.


И зачем эта фича нужна?
Какие-то минимальные расчёты можно и в текущем варианте передать в вызове base.
Если там какая-то сложная логика, то вообще странно в конструктор это пихать.
Тут уже какая-нибудь фабрика нужна, которая и асинхронность сможет предоставить.
Re[2]: почему в C# до сих пор нет наследования конструкторов?
От: Codealot Земля  
Дата: 25.11.22 14:33
Оценка:
Здравствуйте, Baiker, Вы писали:

B>Quebecois прекрасно всё объяснил



Он какой-то бред придумал.
Ад пуст, все бесы здесь.
Re[3]: почему в C# до сих пор нет наследования конструкторов?
От: Sharov Россия  
Дата: 25.11.22 14:36
Оценка:
Здравствуйте, νsb, Вы писали:

vsb>А так нельзя

νsb>
νsb>MyCtor() {
νsb>  int s = something();
νsb>  super(s);
νsb>}
νsb>


Ога, класс, особенно если something виртуальный метод.
Кодом людям нужно помогать!
Re[4]: почему в C# до сих пор нет наследования конструкторов?
От: νsb Казахстан  
Дата: 25.11.22 14:43
Оценка:
Здравствуйте, Sharov, Вы писали:

vsb>>А так нельзя

νsb>>
νsb>>MyCtor() {
νsb>>  int s = something();
νsb>>  super(s);
νsb>>}
νsb>>


S>Ога, класс, особенно если something виртуальный метод.


При чём тут это. Виртуальные методы нельзя вызывать, пока не вызвал конструктор базового объекта.
Re[2]: есть
От: Codealot Земля  
Дата: 25.11.22 15:33
Оценка:
Здравствуйте, Quebecois, Вы писали:

Q>3. Пересобираем ParentAssembly и ChildAssembly. Все собралось без ошибок и зарелизилось.

Q>4. Где-то в другой временной зоне громко ругается разработчик UserAssembly, который начал вываливаться с MethodNotFoundException.

А если ты сигнатуру метода в ParentAssembly.ParentClass поменяешь, который вызывается из UserAssembly?
Ад пуст, все бесы здесь.
Re[5]: почему в C# до сих пор нет наследования конструкторов?
От: Sharov Россия  
Дата: 25.11.22 15:36
Оценка:
Здравствуйте, νsb, Вы писали:

S>>Ога, класс, особенно если something виртуальный метод.

νsb>При чём тут это. Виртуальные методы нельзя вызывать, пока не вызвал конструктор базового объекта.

Если компилятор такое поймать сможет, то да, проблем нет, иначе -- хреново.
Кодом людям нужно помогать!
Re[6]: почему в C# до сих пор нет наследования конструкторов
От: νsb Казахстан  
Дата: 25.11.22 16:08
Оценка:
Здравствуйте, Sharov, Вы писали:

S>>>Ога, класс, особенно если something виртуальный метод.

νsb>>При чём тут это. Виртуальные методы нельзя вызывать, пока не вызвал конструктор базового объекта.

S>Если компилятор такое поймать сможет, то да, проблем нет, иначе -- хреново.


Сможет, конечно. Ну в жаве по крайней мере может.

Проблема возникает, когда нужно что-то не совсем тривиальное. К примеру в конструктор базового класса нужно передавать два параметра. И чтобы эти параметры вычислить, нужна еще пара строк, которые никакие виртуальные методы вызывать не будут, просто какие-то простые преобразования от параметров конструктора. Сейчас это в жаве никак не сделаешь, ну только вычислять это два раза в статическом методе, что смотрится очень некрасиво. Или заменить конструктор на статический метод, который будет конструировать объект, что тоже кажется некрасивым усложнением на ровном месте.

Условно:

Могло бы быть:
MyCtor(double d) {
  double a = atan(d);
  double s = sin(a);
  double c = cos(a);
  super(s, c);
}


А сейчас:

MyCtor(double d) {
  super(s(d), c(d));
}

private static double s(d) {
  double a = atan(d);
  return sin(a);
}

private static double c(d) {
  double a = atan(d);
  return cos(a);
}


или

private MyCtor(double s, c) {
  super(s, c);
}

public static MyCtor of(double d) {
  double a = atan(d);
  double s = sin(a);
  double c = cos(a);
  return new MyCtor(s, c);
}
Отредактировано 25.11.2022 16:12 vsb . Предыдущая версия . Еще …
Отредактировано 25.11.2022 16:11 vsb . Предыдущая версия .
Отредактировано 25.11.2022 16:11 vsb . Предыдущая версия .
Re[3]: есть
От: Quebecois Канада https://www.canada.ca/
Дата: 25.11.22 16:23
Оценка:
Здравствуйте, Codealot, Вы писали:

C>Здравствуйте, Quebecois, Вы писали:


Q>>3. Пересобираем ParentAssembly и ChildAssembly. Все собралось без ошибок и зарелизилось.

Q>>4. Где-то в другой временной зоне громко ругается разработчик UserAssembly, который начал вываливаться с MethodNotFoundException.

C>А если ты сигнатуру метода в ParentAssembly.ParentClass поменяешь, который вызывается из UserAssembly?

Методы на уровне метаданных референсятся из ParentAssembly. Конструкторы, физически находящиеся в ChildAssembly — из нее. Поломка обратной совместимости между ChildAssembly и UserAssembly при модификации BaseAssembly и без модификации ChildAssembly — архитектурный косяк. Еще получается, что из одних и тех же исходников ChildAssembly будет собираться 2 несовместимые друг с другом версии сборки, в зависимости от того, что было в BaseAssembly.

Вообще, золотое правило дизайна сложных систем — каждое изменение отдельного параметра должно иметь минимальные по сложности (и предсказуемости) последствия. Иначе начинается каскад непредвиденных проблем при каждом чихе. В текущей реализации изменение сигнатуры конструктора повлияет только на конкретные методы, конкретно его вызывающие. Они вылетят с ошибками компиляции и необходимость их правки защитит от непреднамеренной поломки на порядок большего количества кода.

Ну и дисклеймер: такие решения 100% субъективны — это judgment call архитектора в плане удобство vs. вероятность глюков. Точек зрения об идеальном балансе тут может быть больше, чем разработчиков, и спорить о них — дело неблагодарное. Обычно, чья зона ответственности — тот и решает (и несет ответственность). Я лишь попытался объяснить точку зрения человека, принявшего решение не наследовать автоматически конструкторы.
Re[4]: есть
От: Codealot Земля  
Дата: 25.11.22 16:28
Оценка:
Здравствуйте, Quebecois, Вы писали:

Ты не ответил на вопрос. Что будет, если ты поменяешь сигнатуру метода в ParentAssembly.ParentClass, который вызывается из UserAssembly?
Ад пуст, все бесы здесь.
Re[3]: есть
От: Quebecois Канада https://www.canada.ca/
Дата: 25.11.22 16:29
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Здравствуйте, Quebecois, Вы писали:


Q>>Явное наследование через base() есть.


K>Если много конструкторов, то замучаешься их вот так дублировать в каждом наследнике и потом поддерживать.

Я же написал ниже: делаешь BaseClass(ConstructionRequest rq) и передаешь его. Runtime overhead будет минимальный, поддерживать ничего не надо. Вся система EventHandler-ов на этом построена.

K>В С++ есть вот такая штука:

K>https://learn.microsoft.com/en-us/cpp/cpp/constructors-cpp?view=msvc-170#inheriting_constructors
K>
K>class Derived : Base
K>{
K>public:
K>    // Inherit all constructors from Base
K>    using Base::Base;
K>...
K>

В плюсах семантика другая — чтобы скомпилировать Derived из исходников, надо рекурсивно распарсить Base и еще миллион заголовков. C# специально разработали так, чтобы вместо этого хватало метаданных сборок из references с хеш-индексами. В итоге, компиляция быстрее на несколько порядков.
Re: почему в C# до сих пор нет наследования конструкторов?
От: Sinclair Россия https://github.com/evilguest/
Дата: 26.11.22 09:23
Оценка: +1
Здравствуйте, Codealot, Вы писали:

C>есть внятные объяснения?

А можно поподробнее объяснить, что имеется в виду?
Какой сценарий использования предполагается?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: есть
От: Sinclair Россия https://github.com/evilguest/
Дата: 26.11.22 09:32
Оценка:
ПЗдравствуйте, Quebecois, Вы писали:


Q>1. Возьмем 3 сборки:

Q> Q>2. Меняем в ParentAssembly int x -> string x.
Q>3. Пересобираем ParentAssembly и ChildAssembly. Все собралось без ошибок и зарелизилось.
Q>4. Где-то в другой временной зоне громко ругается разработчик UserAssembly, который начал вываливаться с MethodNotFoundException.

Простите, но вы неправы. Описанный вами сценарий и не должен работать, и не работает в С# независимо от наличия автоматически унаследованных конструкторов.
Корень проблемы, которую вы описываете в своём примере, не в том, что там что-то автоматически отнаследовалось, а в том, что владелец UserAssembly пытается использовать её без перекомпиляции после того, как одна из зависимостей (ChildAssembly) изменилась.

Дотнет не рассчитан на такое использование. Правило большого пальца: если изменилась хотя бы одна из зависимостей, проект нужно пересобирать.
Исключения из этого правила очень редки, и требуют специальных усилий. Ну, там, к примеру, если вы пишете приложение, которое должно уметь работать с плагинами — в некотором смысле, плагины являются для вас зависимостями; но там вы будете прилагать специальные меры как со стороны приложения, так и со стороны плагинов (в виде набора ограничений на типы, экспортируемые из плагина) для предотвращения проблем с версионированием метаданных.

Авторы .Net, конечно же, предпринимают усилия при публикации апдейтов — так, чтобы приложения, собранные под .Net 6.0.0, продолжали работать под .Net 6.0.3 без перекомпиляции.
И эти усилия, в частности, включают в себя запрет на внезапные модификации сигнатур конструкторов.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: есть
От: karbofos42 Россия  
Дата: 26.11.22 14:17
Оценка:
Здравствуйте, Quebecois, Вы писали:

Q>Я же написал ниже: делаешь BaseClass(ConstructionRequest rq) и передаешь его. Runtime overhead будет минимальный, поддерживать ничего не надо. Вся система EventHandler-ов на этом построена.


ну, допустим, у меня есть базовый класс коллекции со следующими конструкторами:
public CollectionBase();
public CollectionBase(int capacity);
public CollectionBase(params T[] items);
public CollectionBase(IEnumerable<T> items);
public CollectionBase(IReadOnlyCollection<T> items);

как мне сделать наследника, у которого будут все эти конструкторы продублированы? Или как будет выглядеть аналог с ConstructionRequest?

Q>В плюсах семантика другая — чтобы скомпилировать Derived из исходников, надо рекурсивно распарсить Base и еще миллион заголовков. C# специально разработали так, чтобы вместо этого хватало метаданных сборок из references с хеш-индексами. В итоге, компиляция быстрее на несколько порядков.


Методы же как-то наследуются, а конструкторы чем-то принципиально от методов отличаются?
Re[3]: есть
От: Quebecois Канада https://www.canada.ca/
Дата: 26.11.22 16:28
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Простите, но вы неправы. Описанный вами сценарий и не должен работать, и не работает в С# независимо от наличия автоматически унаследованных конструкторов.

Работает.
  пруф
Минимальное репро — распакуется через patch -p1 < RefTest.patch:

diff -urN empty/ChildAssembly/ChildAssembly.csproj RefTest/ChildAssembly/ChildAssembly.csproj
--- empty/ChildAssembly/ChildAssembly.csproj    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/ChildAssembly/ChildAssembly.csproj    2022-11-26 08:16:01.159127600 -0800
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{D75F07F9-EBF7-4E22-A5B8-56CE82B86B1B}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ChildAssembly</RootNamespace>
+    <AssemblyName>ChildAssembly</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Class1.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ParentAssembly\ParentAssembly.csproj">
+      <Project>{1f74baf8-87a4-42d9-a148-d6e06e87c1d5}</Project>
+      <Name>ParentAssembly</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff -urN empty/ChildAssembly/Class1.cs RefTest/ChildAssembly/Class1.cs
--- empty/ChildAssembly/Class1.cs    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/ChildAssembly/Class1.cs    2022-11-26 08:15:52.265586400 -0800
@@ -0,0 +1,17 @@
+using ParentAssembly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ChildAssembly
+{
+    public class ChildClass : ParentClass
+    {
+        public ChildClass(int xyz)
+            : base(xyz)
+        {
+        }
+    }
+}
diff -urN empty/ChildAssembly/Properties/AssemblyInfo.cs RefTest/ChildAssembly/Properties/AssemblyInfo.cs
--- empty/ChildAssembly/Properties/AssemblyInfo.cs    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/ChildAssembly/Properties/AssemblyInfo.cs    2022-11-26 08:14:34.619592300 -0800
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ChildAssembly")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ChildAssembly")]
+[assembly: AssemblyCopyright("Copyright ©  2022")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d75f07f9-ebf7-4e22-a5b8-56ce82b86b1b")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff -urN empty/ParentAssembly/Class1.cs RefTest/ParentAssembly/Class1.cs
--- empty/ParentAssembly/Class1.cs    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/ParentAssembly/Class1.cs    2022-11-26 08:15:28.472597700 -0800
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParentAssembly
+{
+    public class ParentClass
+    {
+        public ParentClass(int xyz)
+        {
+        }
+    }
+}
diff -urN empty/ParentAssembly/ParentAssembly.csproj RefTest/ParentAssembly/ParentAssembly.csproj
--- empty/ParentAssembly/ParentAssembly.csproj    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/ParentAssembly/ParentAssembly.csproj    2022-11-26 08:14:24.240936600 -0800
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>1f74baf8-87a4-42d9-a148-d6e06e87c1d5</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ParentAssembly</RootNamespace>
+    <AssemblyName>ParentAssembly</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System"/>
+    
+    <Reference Include="System.Core"/>
+    <Reference Include="System.Xml.Linq"/>
+    <Reference Include="System.Data.DataSetExtensions"/>
+    
+    
+    <Reference Include="Microsoft.CSharp"/>
+    
+    <Reference Include="System.Data"/>
+    
+    <Reference Include="System.Net.Http"/>
+    
+    <Reference Include="System.Xml"/>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Class1.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ </Project>
diff -urN empty/ParentAssembly/Properties/AssemblyInfo.cs RefTest/ParentAssembly/Properties/AssemblyInfo.cs
--- empty/ParentAssembly/Properties/AssemblyInfo.cs    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/ParentAssembly/Properties/AssemblyInfo.cs    2022-11-26 08:14:24.239935100 -0800
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ParentAssembly")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ParentAssembly")]
+[assembly: AssemblyCopyright("Copyright ©  2022")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1f74baf8-87a4-42d9-a148-d6e06e87c1d5")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff -urN empty/RefTest.sln RefTest/RefTest.sln
--- empty/RefTest.sln    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/RefTest.sln    2022-11-26 08:16:01.162121500 -0800
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32901.215
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserAssembly", "UserAssembly\UserAssembly.csproj", "{F79C2C5C-777C-48FB-B8B3-54B0E73B8D27}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParentAssembly", "ParentAssembly\ParentAssembly.csproj", "{1F74BAF8-87A4-42D9-A148-D6E06E87C1D5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChildAssembly", "ChildAssembly\ChildAssembly.csproj", "{D75F07F9-EBF7-4E22-A5B8-56CE82B86B1B}"
+EndProject
+Global
+    GlobalSection(SolutionConfigurationPlatforms) = preSolution
+        Debug|Any CPU = Debug|Any CPU
+        Release|Any CPU = Release|Any CPU
+    EndGlobalSection
+    GlobalSection(ProjectConfigurationPlatforms) = postSolution
+        {F79C2C5C-777C-48FB-B8B3-54B0E73B8D27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+        {F79C2C5C-777C-48FB-B8B3-54B0E73B8D27}.Debug|Any CPU.Build.0 = Debug|Any CPU
+        {F79C2C5C-777C-48FB-B8B3-54B0E73B8D27}.Release|Any CPU.ActiveCfg = Release|Any CPU
+        {F79C2C5C-777C-48FB-B8B3-54B0E73B8D27}.Release|Any CPU.Build.0 = Release|Any CPU
+        {1F74BAF8-87A4-42D9-A148-D6E06E87C1D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+        {1F74BAF8-87A4-42D9-A148-D6E06E87C1D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+        {1F74BAF8-87A4-42D9-A148-D6E06E87C1D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+        {1F74BAF8-87A4-42D9-A148-D6E06E87C1D5}.Release|Any CPU.Build.0 = Release|Any CPU
+        {D75F07F9-EBF7-4E22-A5B8-56CE82B86B1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+        {D75F07F9-EBF7-4E22-A5B8-56CE82B86B1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+        {D75F07F9-EBF7-4E22-A5B8-56CE82B86B1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+        {D75F07F9-EBF7-4E22-A5B8-56CE82B86B1B}.Release|Any CPU.Build.0 = Release|Any CPU
+    EndGlobalSection
+    GlobalSection(SolutionProperties) = preSolution
+        HideSolutionNode = FALSE
+    EndGlobalSection
+    GlobalSection(ExtensibilityGlobals) = postSolution
+        SolutionGuid = {8D059193-5B4D-4681-8237-6C1A7AAE8BCD}
+    EndGlobalSection
+EndGlobal
diff -urN empty/UserAssembly/App.config RefTest/UserAssembly/App.config
--- empty/UserAssembly/App.config    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/UserAssembly/App.config    2022-11-26 08:14:00.922848100 -0800
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+</configuration>
\ No newline at end of file
diff -urN empty/UserAssembly/Program.cs RefTest/UserAssembly/Program.cs
--- empty/UserAssembly/Program.cs    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/UserAssembly/Program.cs    2022-11-26 08:16:00.178966200 -0800
@@ -0,0 +1,17 @@
+using ChildAssembly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace UserAssembly
+{
+    internal class Program
+    {
+        static void Main(string[] args)
+        {
+            var cls = new ChildClass(123);
+        }
+    }
+}
diff -urN empty/UserAssembly/Properties/AssemblyInfo.cs RefTest/UserAssembly/Properties/AssemblyInfo.cs
--- empty/UserAssembly/Properties/AssemblyInfo.cs    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/UserAssembly/Properties/AssemblyInfo.cs    2022-11-26 08:14:01.067767700 -0800
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("UserAssembly")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("UserAssembly")]
+[assembly: AssemblyCopyright("Copyright ©  2022")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f79c2c5c-777c-48fb-b8b3-54b0e73b8d27")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff -urN empty/UserAssembly/UserAssembly.csproj RefTest/UserAssembly/UserAssembly.csproj
--- empty/UserAssembly/UserAssembly.csproj    1969-12-31 16:00:00.000000000 -0800
+++ RefTest/UserAssembly/UserAssembly.csproj    2022-11-26 08:16:01.161121500 -0800
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{F79C2C5C-777C-48FB-B8B3-54B0E73B8D27}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>UserAssembly</RootNamespace>
+    <AssemblyName>UserAssembly</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ChildAssembly\ChildAssembly.csproj">
+      <Project>{d75f07f9-ebf7-4e22-a5b8-56ce82b86b1b}</Project>
+      <Name>ChildAssembly</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file


UserAssembly создает экземпляр ChildClass без прямого reference на ParentAssembly. Т.е. на уровне метаданных достаточно вещей, вытянутых из ChildAssembly.

S>Корень проблемы, которую вы описываете в своём примере, не в том, что там что-то автоматически отнаследовалось, а в том, что владелец UserAssembly пытается использовать её без перекомпиляции после того, как одна из зависимостей (ChildAssembly) изменилась.

Я не говорю, что это хорошая user practice. Это просто пример того, что сейчас работает, и сломалось бы при добавлении наследования конструкторов.

S>Дотнет не рассчитан на такое использование. Правило большого пальца: если изменилась хотя бы одна из зависимостей, проект нужно пересобирать.

S>Исключения из этого правила очень редки, и требуют специальных усилий. Ну, там, к примеру, если вы пишете приложение, которое должно уметь работать с плагинами — в некотором смысле, плагины являются для вас зависимостями; но там вы будете прилагать специальные меры как со стороны приложения, так и со стороны плагинов (в виде набора ограничений на типы, экспортируемые из плагина) для предотвращения проблем с версионированием метаданных.
Я про плагины и говорил. Сейчас достаточно публичные интерфейсы вынести в отдельную assembly и не трогать существующие типы в ней. С наследованием это перестанет работать.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.