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

82
10 января 2017, 23:48

Допустим, есть класс, который вызывает в методе конструктор и создает тип.

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

Пример:

 public class A
 {
     public void MethodA()
     {
         var b = new B();
     }
 }
 public class B
 {
     public B()
     {
         // каким-то образом неявно получаем ссылку на класс, который вызвал конструктор. 
         // Т.е в данном случае ссылка на экземпляр A
     }
 }

Если не ошибаюсь, то в IL в качестве первого аргумента всегда неявно передается ссылка на вызывающий код.

P.S Задач никаких нету. Интереса ради.

Answer 1

Нет, вы не можете получить экземпляр вызывающего класса. Этой информации нету даже на уровне 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
READ ALSO
Как ратянуть элементы внутри контрола?

Как ратянуть элементы внутри контрола?

На главной форме есть элемент panel в нее подгружается "пользовательский элемент управления", при развертывании окна на весь экран элементы...

56
Не меняется тип колонки в DataGridView

Не меняется тип колонки в DataGridView

Пытаюсь поменять тип колонки из числового на строчный но не получаетсяВ чем может быть проблема ?

116
Как можно улучшить участок кода?

Как можно улучшить участок кода?

Здравствуйте, я только начинаю погружаться в мир разработки, хотелось спросить совета, как можно улучшить следующий код:

105
An entity object cannot be referenced by multiple instances of IEntityChangeTracker

An entity object cannot be referenced by multiple instances of IEntityChangeTracker

Есть 3 сущности - репетитор, студент, группа, все связаны друг с другом через многие ко многимРешил сделать так: объявляю статическую переменную...

111