[Ненормальное программирование, .NET, C#] Вызываем конструктор базового типа в произвольном месте
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Недавно проходил собеседование, и среди прочих был вопрос о порядке вызова конструкторов в C#. После ответа собеседующий решил продемонстрировать эрудицию и заявил, что вот в Java конструктор базового типа можно вызвать в любом месте конструктора производного типа, и C#, конечно, в этом проигрывает.
Утверждение оказалось ложью, враньем и провокацией
SPL
Но это уже не имело значения, потому что вызов был принят.
оригинал
Дисклеймер
SPL
Приведенные ниже приемы не рекомендуется использовать в реальной жизни. Точнее даже рекомендуется не использовать. Это скорее тема для легкого светского разговора с коллегой. Или собеседующим.
Подготовка
Создаем цепочку наследования. Для простоты будем использовать конструкторы без параметров. В конструкторе будем выводить информацию о типе и идентификатор объекта, на котором он вызывается.
public class A
{
public A()
{
Console.WriteLine($"Type '{nameof(A)}' .ctor called on object #{GetHashCode()}");
}
}
public class B : A
{
public B()
{
Console.WriteLine($"Type '{nameof(B)}' .ctor called on object #{GetHashCode()}");
}
}
public class C : B
{
public C()
{
Console.WriteLine($"Type '{nameof(C)}' .ctor called on object #{GetHashCode()}");
}
}
Запускаем программу:
class Program
{
static void Main()
{
new C();
}
}
И получаем вывод:
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Лирическое отступление
SPL
Перед выполнением конструктор может вызвать либо другой конструктор того же типа, либо любой доступный конструктор базового типа. Если вызов не указан явно, компилятор подставит вызов конструктора базового типа без параметров. Если базовый тип не предоставляет такой конструктор, происходит ошибка компиляции. При этом конструктор не может явно вызвать сам себя:
public A() : this() { } // CS0516 Constructor 'A.A()' cannot call itself
и таким фокусом компилятор тоже не провести:
public A() : this(new object()) { }
public A(object _) : this(0) { }
public A(int _) : this() { } // CS0768 Constructor 'A.A(int)' cannot call itself through another constructor
Удаление дублирующегося кода
Добавляем вспомогательный класс:
internal static class Extensions
{
public static void Trace(this object obj) =>
Console.WriteLine($"Type '{obj.GetType().Name}' .ctor called on object #{obj.GetHashCode()}");
}
И заменяем во всех конструкторах
Console.WriteLine($"Type '{nameof(...)}' .ctor called on object #{GetHashCode()}");
на
this.Trace();
Однако теперь программа выводит:
Type 'C' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
В нашем случае можно использовать следующую хитрость. Кто знает о типах времени компиляции? Компилятор. А еще он выбирает перегрузки методов на основе этих типов. И для обобщенных типов и методов генерирует сконструированные сущности тоже он. Поэтому возвращаем правильный вывод типов, переписав метод Trace следующим образом:
public static void Trace<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}");
Получение доступа к конструктору базового типа
Здесь на помощь приходит рефлексия. Добавляем в Extensions метод:
public static Action GetBaseConstructor<T>(this T obj) =>
() => typeof(T)
.BaseType
.GetConstructor(Type.EmptyTypes)
.Invoke(obj, Array.Empty<object>());
В типы B и C добавляем свойство:
private Action @base => this.GetBaseConstructor();
Вызов конструктора базового типа в произвольном месте
Меняем содержимое конструкторов B и C на:
this.Trace();
@base();
Теперь вывод выглядит так:
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Изменение порядка вызова конструкторов базового типа
Внутри типа A создаем вспомогательный тип:
protected class CtorHelper
{
private CtorHelper() { }
}
Так как здесь важна только семантика, конструктор типа целесообразно сделать закрытым. Создание экземпляров не имеет смысла. Тип предназначен исключительно для различения перегрузок конструкторов типа A и производных от него. По этой же причине тип следует разместить внутри A и сделать защищенным.
Добавляем в A, B и C соответствующие конструкторы:
protected A(CtorHelper _) { }
protected B(CtorHelper _) { }
protected C(CtorHelper _) { }
Для типов B и C ко всем конструкторам добавляем вызов:
: base(null)
В результате классы должны выглядеть так
SPL
internal static class Extensions
{
public static Action GetBaseConstructor<T>(this T obj) =>
() => typeof(T)
.BaseType
.GetConstructor(Type.EmptyTypes)
.Invoke(obj, Array.Empty<object>());
public static void Trace<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}");
}
public class A
{
protected A(CtorHelper _) { }
public A()
{
this.Trace();
}
protected class CtorHelper
{
private CtorHelper() { }
}
}
public class B : A
{
private Action @base => this.GetBaseConstructor();
protected B(CtorHelper _) : base(null) { }
public B() : base(null)
{
this.Trace();
@base();
}
}
public class C : B
{
private Action @base => this.GetBaseConstructor();
protected C(CtorHelper _) : base(null) { }
public C() : base(null)
{
this.Trace();
@base();
}
}
И вывод становится:
Type 'C' .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Наивный простачок думает, что обманул компилятор
SPL
Осмысление результата
Добавив в Extensions метод:
public static void TraceSurrogate<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' surrogate .ctor called on object #{obj.GetHashCode()}");
и вызвав его во всех конструкторах, принимающих CtorHelper, мы получим вывод:
Type 'A' surrogate .ctor called on object #58225482
Type 'B' surrogate .ctor called on object #58225482
Type 'C' .ctor called on object #58225482
Type 'A' surrogate .ctor called on object #58225482
Type 'B' .ctor called on object #58225482
Type 'A' .ctor called on object #58225482
Порядок следования конструкторов по принципу базовый/производный, конечно, не изменился. Но все же порядок следования доступных клиентскому коду конструкторов, несущих смысловую нагрузку, удалось поменять благодаря перенаправлению через вызовы недоступных клиенту ничего не делающих вспомогательных конструкторов.
===========
Источник:
habr.com
===========
Похожие новости:
- [Python, Программирование, .NET, Scala] Вы просто не знаете зачем нужны языки с динамической типизацией
- [.NET, C#] Интеграция с «Госуслугами». Место СМЭВ в общей картине (часть I)
- [Open source, .NET, Микросервисы] Как мы разрабатывали кроссплатформенную BPMS
- [.NET, C#, Математика] Тензоры для C#. И матрицы, и векторы, и кастомный тип, и сравнительно быстро
- [Ненормальное программирование, JavaScript, Delphi, Разработка систем связи] Пишем видеочат для локальной сети, или осваиваем WebRTC в 2020 году
- [.NET] Как я понимаю асинхронный код?
- [.NET, C#, Алгоритмы, Разработка игр] A* pathfinding на C#: двоичные кучи и борьба с аллокациями
- [.NET, Open source, Высокая производительность, Интервью] Если ты видишь статью, что язык Х быстрее, чем язык Y – можешь закрывать статью
- [C++, Ненормальное программирование, Программирование] can_throw или не can_throw?
- [Системное администрирование, PostgreSQL, SQL, Администрирование баз данных] SQL HowTo: красивые отчеты по «дырявым» данным — GROUPING SETS
Теги для поиска: #_nenormalnoe_programmirovanie (Ненормальное программирование), #_.net, #_c#, #_nenormalnoe_programmirovanie (ненормальное программирование), #_.net, #_c#, #_nenormalnoe_programmirovanie (
Ненормальное программирование
), #_.net, #_c#
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 06:08
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Недавно проходил собеседование, и среди прочих был вопрос о порядке вызова конструкторов в C#. После ответа собеседующий решил продемонстрировать эрудицию и заявил, что вот в Java конструктор базового типа можно вызвать в любом месте конструктора производного типа, и C#, конечно, в этом проигрывает. Утверждение оказалось ложью, враньем и провокациейSPLНо это уже не имело значения, потому что вызов был принят. оригинал ДисклеймерSPLПриведенные ниже приемы не рекомендуется использовать в реальной жизни. Точнее даже рекомендуется не использовать. Это скорее тема для легкого светского разговора с коллегой. Или собеседующим.
Подготовка Создаем цепочку наследования. Для простоты будем использовать конструкторы без параметров. В конструкторе будем выводить информацию о типе и идентификатор объекта, на котором он вызывается. public class A
{ public A() { Console.WriteLine($"Type '{nameof(A)}' .ctor called on object #{GetHashCode()}"); } } public class B : A { public B() { Console.WriteLine($"Type '{nameof(B)}' .ctor called on object #{GetHashCode()}"); } } public class C : B { public C() { Console.WriteLine($"Type '{nameof(C)}' .ctor called on object #{GetHashCode()}"); } } Запускаем программу: class Program
{ static void Main() { new C(); } } И получаем вывод: Type 'A' .ctor called on object #58225482 Type 'B' .ctor called on object #58225482 Type 'C' .ctor called on object #58225482 Лирическое отступлениеSPLПеред выполнением конструктор может вызвать либо другой конструктор того же типа, либо любой доступный конструктор базового типа. Если вызов не указан явно, компилятор подставит вызов конструктора базового типа без параметров. Если базовый тип не предоставляет такой конструктор, происходит ошибка компиляции. При этом конструктор не может явно вызвать сам себя:
public A() : this() { } // CS0516 Constructor 'A.A()' cannot call itself
и таким фокусом компилятор тоже не провести: public A() : this(new object()) { }
public A(object _) : this(0) { } public A(int _) : this() { } // CS0768 Constructor 'A.A(int)' cannot call itself through another constructor Удаление дублирующегося кода Добавляем вспомогательный класс: internal static class Extensions
{ public static void Trace(this object obj) => Console.WriteLine($"Type '{obj.GetType().Name}' .ctor called on object #{obj.GetHashCode()}"); } И заменяем во всех конструкторах Console.WriteLine($"Type '{nameof(...)}' .ctor called on object #{GetHashCode()}");
на this.Trace();
Однако теперь программа выводит: Type 'C' .ctor called on object #58225482 Type 'C' .ctor called on object #58225482 Type 'C' .ctor called on object #58225482 В нашем случае можно использовать следующую хитрость. Кто знает о типах времени компиляции? Компилятор. А еще он выбирает перегрузки методов на основе этих типов. И для обобщенных типов и методов генерирует сконструированные сущности тоже он. Поэтому возвращаем правильный вывод типов, переписав метод Trace следующим образом: public static void Trace<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}"); Получение доступа к конструктору базового типа Здесь на помощь приходит рефлексия. Добавляем в Extensions метод: public static Action GetBaseConstructor<T>(this T obj) =>
() => typeof(T) .BaseType .GetConstructor(Type.EmptyTypes) .Invoke(obj, Array.Empty<object>()); В типы B и C добавляем свойство: private Action @base => this.GetBaseConstructor();
Вызов конструктора базового типа в произвольном месте Меняем содержимое конструкторов B и C на: this.Trace();
@base(); Теперь вывод выглядит так: Type 'A' .ctor called on object #58225482 Type 'B' .ctor called on object #58225482 Type 'A' .ctor called on object #58225482 Type 'C' .ctor called on object #58225482 Type 'A' .ctor called on object #58225482 Type 'B' .ctor called on object #58225482 Type 'A' .ctor called on object #58225482 Изменение порядка вызова конструкторов базового типа Внутри типа A создаем вспомогательный тип: protected class CtorHelper
{ private CtorHelper() { } } Так как здесь важна только семантика, конструктор типа целесообразно сделать закрытым. Создание экземпляров не имеет смысла. Тип предназначен исключительно для различения перегрузок конструкторов типа A и производных от него. По этой же причине тип следует разместить внутри A и сделать защищенным. Добавляем в A, B и C соответствующие конструкторы: protected A(CtorHelper _) { }
protected B(CtorHelper _) { } protected C(CtorHelper _) { } Для типов B и C ко всем конструкторам добавляем вызов: : base(null)
В результате классы должны выглядеть такSPLinternal static class Extensions
{ public static Action GetBaseConstructor<T>(this T obj) => () => typeof(T) .BaseType .GetConstructor(Type.EmptyTypes) .Invoke(obj, Array.Empty<object>()); public static void Trace<T>(this T obj) => Console.WriteLine($"Type '{typeof(T).Name}' .ctor called on object #{obj.GetHashCode()}"); } public class A { protected A(CtorHelper _) { } public A() { this.Trace(); } protected class CtorHelper { private CtorHelper() { } } } public class B : A { private Action @base => this.GetBaseConstructor(); protected B(CtorHelper _) : base(null) { } public B() : base(null) { this.Trace(); @base(); } } public class C : B { private Action @base => this.GetBaseConstructor(); protected C(CtorHelper _) : base(null) { } public C() : base(null) { this.Trace(); @base(); } } И вывод становится: Type 'C' .ctor called on object #58225482 Type 'B' .ctor called on object #58225482 Type 'A' .ctor called on object #58225482 Наивный простачок думает, что обманул компиляторSPLОсмысление результата Добавив в Extensions метод: public static void TraceSurrogate<T>(this T obj) =>
Console.WriteLine($"Type '{typeof(T).Name}' surrogate .ctor called on object #{obj.GetHashCode()}"); и вызвав его во всех конструкторах, принимающих CtorHelper, мы получим вывод: Type 'A' surrogate .ctor called on object #58225482 Type 'B' surrogate .ctor called on object #58225482 Type 'C' .ctor called on object #58225482 Type 'A' surrogate .ctor called on object #58225482 Type 'B' .ctor called on object #58225482 Type 'A' .ctor called on object #58225482 Порядок следования конструкторов по принципу базовый/производный, конечно, не изменился. Но все же порядок следования доступных клиентскому коду конструкторов, несущих смысловую нагрузку, удалось поменять благодаря перенаправлению через вызовы недоступных клиенту ничего не делающих вспомогательных конструкторов. =========== Источник: habr.com =========== Похожие новости:
Ненормальное программирование ), #_.net, #_c# |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 06:08
Часовой пояс: UTC + 5