Страницы

Поиск по вопросам

суббота, 13 октября 2018 г.

Можно ли неявно получить ссылку на класс из которого был вызван конструктор другого класса?

Допустим, есть класс, который вызывает в методе конструктор и создает тип.
Можно ли неявно в конструкторе класса получить ссылку на класс без явной передачи this в конструктор?
Пример:
public class A { public void MethodA() { var b = new B(); } }
public class B { public B() { // каким-то образом неявно получаем ссылку на класс, который вызвал конструктор. // Т.е в данном случае ссылка на экземпляр A } }
Если не ошибаюсь, то в IL в качестве первого аргумента всегда неявно передается ссылка на вызывающий код.
P.S Задач никаких нету. Интереса ради.


Ответ

Нет, вы не можете получить экземпляр вызывающего класса. Этой информации нету даже на уровне IL. Конструктор B, декомпилированный в ILDasm, выглядит так:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Размер кода: 9 (0x9) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ret } // end of method B::.ctor
У него нету объявления параметров, так что скрытых параметров нету.

То, что вы можете выяснить — это какой конкретно метод вас вызывает, без указания на экземпляр класса. Это делается так:
[MethodImpl(MethodImplOptions.NoInlining)] public B() { MethodBase callingMethod = new StackFrame(1).GetMethod(); Console.WriteLine($"Called from type: {callingMethod.DeclaringType.FullName}, " + $"calling method name: {callingMethod.Name}"); }
У меня выводит:
Called from type: Test.A, calling method name: MethodA
Вы создаёте stack frame, начинающийся на 1 выше вашего текущего фрейма, и запрашиваете метод. Имея reflection-дескриптор метода, вы можете получить информацию из него.
Заметьте, что я применил атрибут MethodImplOptions.NoInlining, чтобы запретить встраивать этот метод в точку вызова, в противном случае в stack trace могла бы попасть не та информация.
Ещё один немаловажный момент: запрос StackFrame — затратная, дорогостоящая операция, поэтому не стоит применять это решение в production-коде. Если вы хотите информацию о том. кто вас вызвал, в production-коде, стоит доверить это компилятору и воспользоваться атрибутом [CallerMemberName], доступным начиная с .NET 4.5:
public B([CallerMemberName] string callerName = null, [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLineNumber = -1) { Console.WriteLine($"Called from method: {callerName}, " + $"located {callerFile}@{callerLineNumber}"); }
Выводит:
Called from method: MethodA, located D:\full path here\Test\Program.cs@42

Комментариев нет:

Отправить комментарий