Bagaimana saya dapat menemukan metode yang disebut metode saat ini?

503

Saat masuk C #, bagaimana saya bisa mempelajari nama metode yang disebut metode saat ini? Saya tahu semua tentang System.Reflection.MethodBase.GetCurrentMethod(), tetapi saya ingin pergi satu langkah di bawah ini di jejak tumpukan. Saya telah mempertimbangkan untuk mengurai jejak stack, tetapi saya berharap untuk menemukan cara yang lebih bersih dan lebih eksplisit, seperti Assembly.GetCallingAssembly()tetapi untuk metode.

flipdoubt
sumber
22
Jika Anda menggunakan .net 4.5 beta +, Anda dapat menggunakan CallerInformation API .
Rohit Sharma
5
Informasi Penelepon juga jauh lebih cepat
menyelam
4
Saya membuat tolok ukur BenchmarkDotNet cepat dari tiga metode utama ( StackTrace, StackFramedan CallerMemberName) dan memposting hasilnya sebagai intisari untuk dilihat orang lain di sini: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Jawaban:

513

Coba ini:

using System.Diagnostics;
// Get call stack
StackTrace stackTrace = new StackTrace(); 
// Get calling method name
Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);

satu garis:

(new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod().Name

Itu dari Get Calling Method menggunakan Reflection [C #] .

Firas Assaad
sumber
12
Anda juga dapat membuat hanya bingkai yang Anda butuhkan, daripada seluruh tumpukan:
Joel Coehoorn
187
StackFrame baru (1) .GetMethod (). Name;
Joel Coehoorn
12
Ini tidak sepenuhnya dapat diandalkan. Mari kita lihat apakah ini berfungsi dalam komentar! Coba yang berikut ini di aplikasi konsol dan Anda melihat bahwa optilator kompilasi merusaknya. static void Main (string [] args) {CallIt (); } private static void CallIt () {Final (); } static void Final () {StackTrace trace = new StackTrace (); StackFrame frame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Name); }
BlackWasp
10
Ini tidak berfungsi ketika kompiler inline atau tail-call mengoptimalkan metode, dalam hal ini tumpukan diciutkan dan Anda akan menemukan nilai-nilai lain dari yang diharapkan. Ketika Anda hanya menggunakan ini di build Debug, itu akan bekerja dengan baik.
Abel
46
Apa yang telah saya lakukan di masa lalu adalah menambahkan atribut kompiler [MethodImplAttribute (MethodImplOptions.NoInlining)] sebelum metode yang akan mencari jejak stack. Itu memastikan bahwa kompiler tidak akan sejalan metode, dan jejak tumpukan akan berisi metode panggilan yang sebenarnya (saya tidak khawatir tentang rekursi ekor dalam banyak kasus.)
Jordan Rieger
363

Di C # 5 Anda bisa mendapatkan informasi itu menggunakan info penelepon :

//using System.Runtime.CompilerServices;
public void SendError(string Message, [CallerMemberName] string callerName = "") 
{ 
    Console.WriteLine(callerName + "called me."); 
} 

Anda juga bisa mendapatkan [CallerFilePath]dan [CallerLineNumber].

Coincoin
sumber
13
Halo, ini bukan C # 5, tersedia di 4.5.
SETELAH
35
Versi @AF Bahasa (C #) tidak sama dengan versi .NET.
kwesolowski
6
@stuartd Sepertinya [CallerTypeName]dihapus dari framework .Net saat ini (4.6.2) dan Core CLR
Ph0en1x
4
@ Ph0en1x itu tidak pernah dalam kerangka, maksud saya adalah akan berguna jika itu, misalnya bagaimana cara mendapatkan Ketik nama CallerMember
stuartd
3
@DiegoDeberdt - Saya pernah membaca bahwa menggunakan ini tidak memiliki kelemahan karena melakukan semua pekerjaan pada waktu kompilasi. Saya percaya ini akurat untuk apa yang disebut metode ini.
cchamberlain
109

Anda dapat menggunakan Informasi Penelepon dan parameter opsional:

public static string WhoseThere([CallerMemberName] string memberName = "")
{
       return memberName;
}

Tes ini menggambarkan hal ini:

[Test]
public void Should_get_name_of_calling_method()
{
    var methodName = CachingHelpers.WhoseThere();
    Assert.That(methodName, Is.EqualTo("Should_get_name_of_calling_method"));
}

Sementara StackTrace bekerja sangat cepat di atas dan tidak akan menjadi masalah kinerja dalam banyak kasus, Informasi Penelepon masih jauh lebih cepat. Dalam sampel 1000 iterasi, saya mencatatnya 40 kali lebih cepat.

merpati
sumber
Hanya tersedia dari. Net 4.5
DerApe
1
Perhatikan bahwa ini tidak berfungsi, jika penelepon meneruskan agrument: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"karena CallerMemberNameini hanya menggantikan nilai default.
Olivier Jacot-Descombes
@ OlivierJacot-Descombes tidak berfungsi dengan cara yang sama seperti metode ekstensi tidak akan berfungsi jika Anda memberikan parameter padanya. Anda dapat menemukan parameter string lain yang dapat digunakan. Juga catat bahwa resharper akan memberi Anda peringatan jika Anda mencoba menyampaikan argumen seperti yang Anda lakukan.
menyelam
1
@dove Anda dapat thismemasukkan parameter eksplisit apa pun ke dalam metode ekstensi. Juga, Olivier benar, Anda dapat memberikan nilai dan [CallerMemberName]tidak diterapkan; alih-alih berfungsi sebagai pengganti di mana nilai default biasanya digunakan. Faktanya, jika kita melihat IL kita dapat melihat bahwa metode yang dihasilkan tidak berbeda dari apa yang biasanya dipancarkan untuk [opt]arg, injeksi CallerMemberNamekarena itu merupakan perilaku CLR. Terakhir, dokumen: "Atribut Info Penelepon [...] memengaruhi nilai default yang diteruskan ketika argumen dihilangkan "
Shaun Wilson
2
Ini sempurna dan asyncramah yang StackFrametidak akan membantu Anda. Juga tidak mempengaruhi dipanggil dari lambda.
Aaron
65

Rekap cepat dari 2 pendekatan dengan perbandingan kecepatan menjadi bagian penting.

http://geekswithblogs.net/BlackRabbitCoder/archive/2013/07/25/c.net-little-wonders-getting-caller-information.aspx

Menentukan penelepon pada waktu kompilasi

static void Log(object message, 
[CallerMemberName] string memberName = "",
[CallerFilePath] string fileName = "",
[CallerLineNumber] int lineNumber = 0)
{
    // we'll just use a simple Console write for now    
    Console.WriteLine("{0}({1}):{2} - {3}", fileName, lineNumber, memberName, message);
}

Menentukan penelepon menggunakan tumpukan

static void Log(object message)
{
    // frame 1, true for source info
    StackFrame frame = new StackFrame(1, true);
    var method = frame.GetMethod();
    var fileName = frame.GetFileName();
    var lineNumber = frame.GetFileLineNumber();

    // we'll just use a simple Console write for now    
    Console.WriteLine("{0}({1}):{2} - {3}", fileName, lineNumber, method.Name, message);
}

Perbandingan kedua pendekatan tersebut

Time for 1,000,000 iterations with Attributes: 196 ms
Time for 1,000,000 iterations with StackTrace: 5096 ms

Jadi Anda tahu, menggunakan atribut jauh, jauh lebih cepat! Nyaris 25x lebih cepat sebenarnya.

Tikall
sumber
Metode ini tampaknya merupakan pendekatan yang unggul. Ini juga berfungsi di Xamarin tanpa masalah ruang nama tidak tersedia.
lyndon hughey
63

Kita dapat memperbaiki kode Mr Assad (jawaban yang diterima saat ini) hanya sedikit dengan membuat instance hanya frame yang sebenarnya kita butuhkan daripada seluruh tumpukan:

new StackFrame(1).GetMethod().Name;

Ini mungkin melakukan sedikit lebih baik, meskipun kemungkinan masih harus menggunakan tumpukan penuh untuk membuat bingkai tunggal itu. Juga, itu masih memiliki peringatan yang sama yang ditunjukkan oleh Alex Lyman (pengoptimal / kode asli mungkin merusak hasil). Akhirnya, Anda mungkin ingin memeriksa untuk memastikannya new StackFrame(1)atau .GetFrame(1)tidak kembalinull , tidak mungkin seperti kemungkinan itu muncul.

Lihat pertanyaan terkait ini: Dapatkah Anda menggunakan refleksi untuk menemukan nama metode yang sedang dijalankan?

Joel Coehoorn
sumber
1
apakah mungkin new ClassName(…)sama dengan nol?
Nama Tampilan
1
Yang menyenangkan adalah ini juga berfungsi di .NET Standard 2.0.
srsedate
60

Secara umum, Anda bisa menggunakan System.Diagnostics.StackTracekelas untuk mendapatkan System.Diagnostics.StackFrame, dan kemudian menggunakan GetMethod()metode untuk mendapatkan System.Reflection.MethodBaseobjek. Namun, ada beberapa peringatan untuk pendekatan ini:

  1. Itu mewakili tumpukan runtime - optimisasi bisa sebaris metode, dan Anda tidak akan melihat metode itu di jejak tumpukan.
  2. Ini tidak akan menampilkan bingkai asli apa pun, jadi jika ada kemungkinan metode Anda dipanggil oleh metode asli, ini tidak akan berfungsi, dan sebenarnya tidak ada cara yang tersedia saat ini untuk melakukannya.

( CATATAN: Saya hanya memperluas jawaban yang diberikan oleh Firas Assad .)

Alex Lyman
sumber
2
Dalam mode debug dengan optimisasi dimatikan, apakah Anda dapat melihat apa metode dalam jejak tumpukan?
AttackingHobo
1
@AttackingHobo: Ya - kecuali jika metode ini digarisbawahi (optimisasi aktif) atau bingkai asli, Anda akan melihatnya.
Alex Lyman
38

Pada. NET 4.5 Anda dapat menggunakan Atribut Informasi Penelepon :

  • CallerFilePath - File sumber yang disebut fungsi;
  • CallerLineNumber - Baris kode yang disebut fungsi;
  • CallerMemberName - Anggota yang memanggil fungsi.

    public void WriteLine(
        [CallerFilePath] string callerFilePath = "", 
        [CallerLineNumber] long callerLineNumber = 0,
        [CallerMemberName] string callerMember= "")
    {
        Debug.WriteLine(
            "Caller File Path: {0}, Caller Line Number: {1}, Caller Member: {2}", 
            callerFilePath,
            callerLineNumber,
            callerMember);
    }

 

Fasilitas ini juga hadir dalam ".NET Core" dan ".NET Standard".

Referensi

  1. Microsoft - Informasi Penelepon (C #)
  2. Microsoft - CallerFilePathAttributeKelas
  3. Microsoft - CallerLineNumberAttributeKelas
  4. Microsoft - CallerMemberNameAttributeKelas
Ivan Pinto
sumber
15

Perhatikan bahwa hal itu tidak akan dapat diandalkan dalam kode rilis, karena optimasi. Selain itu, menjalankan aplikasi dalam mode kotak pasir (berbagi jaringan) tidak akan memungkinkan Anda untuk mengambil bingkai tumpukan sama sekali.

Pertimbangkan pemrograman berorientasi aspek (AOP), seperti PostSharp , yang alih-alih dipanggil dari kode Anda, memodifikasi kode Anda, dan karenanya tahu di mana ia berada setiap saat.

Lasse V. Karlsen
sumber
Anda sepenuhnya benar bahwa ini tidak akan berfungsi dalam rilis. Saya tidak yakin saya suka ide injeksi kode, tapi saya kira dalam arti pernyataan debug memerlukan modifikasi kode, tapi tetap saja. Mengapa tidak kembali saja ke makro C? Setidaknya itu sesuatu yang bisa Anda lihat.
ebyrob
9

Jelas ini adalah jawaban yang terlambat, tetapi saya memiliki pilihan yang lebih baik jika Anda dapat menggunakan .NET 4.5 atau lebih:

internal static void WriteInformation<T>(string text, [CallerMemberName]string method = "")
{
    Console.WriteLine(DateTime.Now.ToString() + " => " + typeof(T).FullName + "." + method + ": " + text);
}

Ini akan mencetak Tanggal dan Waktu saat ini, diikuti oleh "Namespace.ClassName.MethodName" dan diakhiri dengan ": teks".
Output sampel:

6/17/2016 12:41:49 PM => WpfApplication.MainWindow..ctor: MainWindow initialized

Penggunaan sampel:

Logger.WriteInformation<MainWindow>("MainWindow initialized");
Camilo Terevinto
sumber
8
/// <summary>
/// Returns the call that occurred just before the "GetCallingMethod".
/// </summary>
public static string GetCallingMethod()
{
   return GetCallingMethod("GetCallingMethod");
}

/// <summary>
/// Returns the call that occurred just before the the method specified.
/// </summary>
/// <param name="MethodAfter">The named method to see what happened just before it was called. (case sensitive)</param>
/// <returns>The method name.</returns>
public static string GetCallingMethod(string MethodAfter)
{
   string str = "";
   try
   {
      StackTrace st = new StackTrace();
      StackFrame[] frames = st.GetFrames();
      for (int i = 0; i < st.FrameCount - 1; i++)
      {
         if (frames[i].GetMethod().Name.Equals(MethodAfter))
         {
            if (!frames[i + 1].GetMethod().Name.Equals(MethodAfter)) // ignores overloaded methods.
            {
               str = frames[i + 1].GetMethod().ReflectedType.FullName + "." + frames[i + 1].GetMethod().Name;
               break;
            }
         }
      }
   }
   catch (Exception) { ; }
   return str;
}
Flanders
sumber
oops, saya seharusnya menjelaskan param "MethodAfter" sedikit lebih baik. Jadi, jika Anda memanggil metode ini dalam fungsi tipe "log", Anda akan ingin mendapatkan metode setelah fungsi "log". jadi Anda akan memanggil GetCallingMethod ("log"). -Cheers
Flanders
6

Mungkin Anda mencari sesuatu seperti ini:

StackFrame frame = new StackFrame(1);
frame.GetMethod().Name; //Gets the current method name

MethodBase method = frame.GetMethod();
method.DeclaringType.Name //Gets the current class name
jesal
sumber
4
private static MethodBase GetCallingMethod()
{
  return new StackFrame(2, false).GetMethod();
}

private static Type GetCallingType()
{
  return new StackFrame(2, false).GetMethod().DeclaringType;
}

Kelas yang fantastis ada di sini: http://www.csharp411.com/c-get-calling-method/

Tebo
sumber
StackFrame tidak dapat diandalkan. Naik "2 bingkai" mungkin dengan mudah kembali juga panggilan metode.
user2864740
2

Pendekatan lain yang saya gunakan adalah menambahkan parameter ke metode yang dimaksud. Misalnya, alih-alih void Foo(), gunakan void Foo(string context). Kemudian masukkan beberapa string unik yang menunjukkan konteks panggilan.

Jika Anda hanya membutuhkan penelepon / konteks untuk pengembangan, Anda dapat menghapus paramsebelum pengiriman.

GregUzelac
sumber
2

Untuk mendapatkan Nama Metode dan Nama Kelas coba ini:

    public static void Call()
    {
        StackTrace stackTrace = new StackTrace();

        var methodName = stackTrace.GetFrame(1).GetMethod();
        var className = methodName.DeclaringType.Name.ToString();

        Console.WriteLine(methodName.Name + "*****" + className );
    }
Arian
sumber
1
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;

akan cukup, saya pikir.

caner
sumber
1

Lihatlah nama metode Logging di .NET . Waspadalah menggunakannya dalam kode produksi. StackFrame mungkin tidak dapat diandalkan ...

Yuval Peled
sumber
5
Ringkasan konten akan menyenangkan.
Peter Mortensen
1

Kita juga dapat menggunakan lambda untuk menemukan penelepon.

Misalkan Anda memiliki metode yang ditentukan oleh Anda:

public void MethodA()
    {
        /*
         * Method code here
         */
    }

dan Anda ingin menemukan itu penelepon.

1 . Ubah tanda tangan metode sehingga kami memiliki parameter tipe Action (Func juga akan berfungsi):

public void MethodA(Action helperAction)
        {
            /*
             * Method code here
             */
        }

2 . Nama Lambda tidak dihasilkan secara acak. Aturannya tampaknya:> <CallerMethodName> __X di mana CallerMethodName digantikan oleh fungsi sebelumnya dan X adalah indeks.

private MethodInfo GetCallingMethodInfo(string funcName)
    {
        return GetType().GetMethod(
              funcName.Substring(1,
                                funcName.IndexOf("&gt;", 1, StringComparison.Ordinal) - 1)
              );
    }

3 . Ketika kita memanggil MethodA, parameter Action / Func harus dihasilkan oleh metode pemanggil. Contoh:

MethodA(() => {});

4 . Di dalam MethodA kita sekarang dapat memanggil fungsi helper yang didefinisikan di atas dan menemukan MethodInfo dari metode pemanggil.

Contoh:

MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);
smiron
sumber
0

Informasi tambahan untuk jawaban Firas Assaad.

Saya telah menggunakan new StackFrame(1).GetMethod().Name;.net core 2.1 dengan injeksi ketergantungan dan saya mendapatkan metode panggilan sebagai 'Mulai'.

Saya mencoba [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" dan itu memberi saya metode panggilan yang benar

cdev
sumber
-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
Mauro Sala
sumber
1
saya tidak downvote, tetapi ingin mencatat bahwa menambahkan beberapa teks untuk menjelaskan mengapa Anda memposting informasi yang sangat mirip (tahun kemudian) dapat meningkatkan nilai pertanyaan dan menghindari downvoting lebih lanjut.
Shaun Wilson