Bisakah Anda menggunakan refleksi untuk menemukan nama metode yang sedang dijalankan?

202

Seperti judulnya katakan: Dapatkah refleksi memberi Anda nama metode yang sedang dijalankan.

Saya cenderung tidak menebak, karena masalah Heisenberg. Bagaimana Anda memanggil metode yang akan memberi tahu Anda metode saat ini tanpa mengubah apa metode saat ini? Tapi saya berharap seseorang bisa membuktikan saya salah di sana.

Memperbarui:

  • Bagian 2: Bisakah ini digunakan untuk mencari kode di dalam properti juga?
  • Bagian 3: Seperti apa performanya?

Hasil Akhir yang
saya pelajari tentang MethodBase.GetCurrentMethod (). Saya juga belajar bahwa saya tidak hanya dapat membuat jejak stack, saya hanya dapat membuat frame persis yang saya butuhkan jika saya mau.

Untuk menggunakan ini di dalam properti, cukup ambil. Substring (4) untuk menghapus 'set_' atau 'get_'.

Joel Coehoorn
sumber
Joel, saya tahu ini pertanyaan lama, tetapi apa yang Anda maksud dengan membuat kerangka metode yang tepat?
Abhijeet
Ini merujuk ke item tertentu dalam tumpukan panggilan: bagian dari jejak tumpukan yang penting.
Joel Coehoorn

Jawaban:

119

Pada .NET 4.5 Anda juga dapat menggunakan [CallerMemberName]

Contoh: penyetel properti (untuk menjawab bagian 2):

    protected void SetProperty<T>(T value, [CallerMemberName] string property = null)
    {
        this.propertyValues[property] = value;
        OnPropertyChanged(property);
    }

    public string SomeProperty
    {
        set { SetProperty(value); }
    }

Compiler akan menyediakan string literal yang cocok di callsites, jadi pada dasarnya tidak ada overhead kinerja.

John Nilsson
sumber
3
Ini bagus! Saya menggunakan StackFrame(1)metode yang dijelaskan dalam jawaban lain untuk penebangan, yang tampaknya bekerja sampai Jitter memutuskan untuk mulai menyelaraskan berbagai hal. Saya tidak ingin menambahkan atribut untuk mencegah inlining karena alasan kinerja. Menggunakan [CallerMemberName]pendekatan memperbaiki masalah. Terima kasih!
Brian Rogers
5
[CallerMemberName] tersedia di net 4 dengan build BCL dikemas
Venson
2
Mempertimbangkan bahwa menggunakan StackFrame (1) dalam mode debug harus berfungsi. Tetapi ketika menggunakan mode rilis saat kompilasi, mungkin ada beberapa optimasi dan tumpukan mungkin tidak seperti yang Anda harapkan.
Axel O'Connell
Bukankah ini akan mengembalikan anggota panggilan (yaitu SomeProperty), bukannya metode yang sedang dijalankan?
Lennart
1
Ya, memohon setter akan menghasilkan panggilan untuk OnPropertyChanged("SomeProperty")dan tidakOnPropertyChanged("SetProperty")
John Nilsson
189

Untuk non- asyncmetode, seseorang dapat menggunakan

System.Reflection.MethodBase.GetCurrentMethod().Name;

https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.getcurrentmethod

Harap diingat bahwa untuk asyncmetode ini akan mengembalikan "MoveNext".

Ed Guinness
sumber
8
Ketahuilah bahwa ini tidak selalu menghasilkan hasil yang diharapkan. Yaitu, metode atau properti kecil sering diuraikan dalam rilis build, dalam hal ini hasilnya akan menjadi nama metode pemanggil.
Abel
5
Sejauh yang saya tahu, tidak. Karena dalam runtime, MSIL tidak tersedia lagi dari pointer eksekusi (ini JITted). Anda masih dapat menggunakan refleksi jika Anda tahu nama metode. Intinya adalah, ketika digarisbawahi, metode yang sedang dijalankan sekarang adalah metode lain (yaitu, satu atau lebih tinggi tumpukan). Dengan kata lain, metode itu menghilang. Bahkan jika Anda menandai metode Anda dengan NoInlining, masih ada kemungkinan metode ini dioptimalkan untuk tail-call, dalam hal ini juga hilang. Namun, ini akan berfungsi saat dalam proses debug.
Abel
1
Untuk menghindari inline, tambahkan atribut [MethodImpl (MethodImplOptions.NoInlining)] di atas metode.
alex.peter
asyncMetode di dalam Anda kemungkinan besar akan mendapatkan "MoveNext" sebagai nama metode.
Victor Yarema
46

Cuplikan yang disediakan oleh Lex agak panjang, jadi saya menunjukkan bagian penting karena tidak ada orang lain yang menggunakan teknik yang sama persis:

string MethodName = new StackFrame(0).GetMethod().Name;

Ini harus mengembalikan hasil yang identik ke MethodBase.GetCurrentMethod (). Teknik nama , tapi masih layak untuk ditunjukkan karena saya bisa menerapkan ini sekali dalam metode sendiri menggunakan indeks 1 untuk metode sebelumnya dan memanggilnya dari sejumlah properti yang berbeda. Selain itu, ia hanya mengembalikan satu frame ketimbang seluruh jejak tumpukan:

private string GetPropertyName()
{  //.SubString(4) strips the property prefix (get|set) from the name
    return new StackFrame(1).GetMethod().Name.Substring(4);
}

Ini juga satu kalimat;)

Joel Coehoorn
sumber
dapat berupa public static string GetPropertyName () di kelas helper? metode statis?
Kiquenet
2
Sama seperti dengan jawaban Ed Guiness: tumpukan bisa berbeda dalam rilis rilis dan metode pertama mungkin tidak sama dengan metode saat ini dalam kasus inlining atau optimasi panggilan ekor.
Abel
Lihat jawaban John Nilsson untuk cara yang bagus untuk mengatasi masalah inlining jika Anda menggunakan .Net 4.5.
Brian Rogers
ini bisa lebih baik daripada jawaban yang diterima dan jawaban di atas juga
T.Todua
16

Coba ini di dalam metode Utama di program konsol kosong:

MethodBase method = MethodBase.GetCurrentMethod();
Console.WriteLine(method.Name);

Output Konsol:
Main

Lars Mæhlum
sumber
12

Iya tentu saja.

Jika Anda ingin memanipulasi objek, saya sebenarnya menggunakan fungsi seperti ini:

public static T CreateWrapper<T>(Exception innerException, params object[] parameterValues) where T : Exception, new()
{
    if (parameterValues == null)
    {
        parameterValues = new object[0];
    }

    Exception exception   = null;
    StringBuilder builder = new StringBuilder();
    MethodBase method     = new StackFrame(2).GetMethod();
    ParameterInfo[] parameters = method.GetParameters();
    builder.AppendFormat(CultureInfo.InvariantCulture, ExceptionFormat, new object[] { method.DeclaringType.Name, method.Name });
    if ((parameters.Length > 0) || (parameterValues.Length > 0))
    {
        builder.Append(GetParameterList(parameters, parameterValues));
    }

    exception = (Exception)Activator.CreateInstance(typeof(T), new object[] { builder.ToString(), innerException });
    return (T)exception;
}

Garis ini:

MethodBase method     = new StackFrame(2).GetMethod();

Menaiki bingkai tumpukan untuk menemukan metode pemanggilan kemudian kami menggunakan refleksi untuk mendapatkan nilai informasi parameter yang diteruskan ke sana untuk fungsi pelaporan kesalahan umum. Untuk mendapatkan metode saat ini, cukup gunakan bingkai tumpukan saat ini (1).

Seperti yang orang lain katakan untuk nama metode saat ini, Anda juga dapat menggunakan:

MethodBase.GetCurrentMethod()

Saya lebih suka berjalan stack karena jika melihat secara internal pada metode itu hanya menciptakan StackCrawlMark. Mengatasi Stack secara langsung tampak lebih jelas bagi saya

Posting 4.5 Anda sekarang dapat menggunakan [CallerMemberNameAttribute] sebagai bagian dari parameter metode untuk mendapatkan string dari nama metode - ini dapat membantu dalam beberapa skenario (tetapi benar-benar mengatakan contoh di atas)

public void Foo ([CallerMemberName] string methodName = null)

Ini tampaknya terutama solusi untuk dukungan INotifyPropertyChanged di mana sebelumnya Anda memiliki string yang berserakan di seluruh kode acara Anda.

Lex
sumber
Tidak konyol; Saya hanya melewatinya. Anda mungkin bisa melakukan sesuatu untuk membuatnya lebih mudah dilihat tetapi upaya untuk menghargai rasio tampaknya lebih suka membuatnya tetap sederhana. Pada dasarnya dev hanya menyalin dalam daftar parameter tanda tangan metode (menghapus jenis saja).
Lex
apa itu: ExceptionFormat dan GetParameterList?
Kiquenet
Cara terlambat dalam menjawab tetapi: ExceptionFormat adalah format string konstan dan GetParameterList adalah fungsi sederhana yang memformat parameter dengan nilai-nilai (Anda bisa melakukan ini sebaris)
Lex
11

Membandingkan cara untuk mendapatkan nama metode - menggunakan konstruk pewaktuan arbitrer di LinqPad:

KODE

void Main()
{
    // from http://blogs.msdn.com/b/webdevelopertips/archive/2009/06/23/tip-83-did-you-know-you-can-get-the-name-of-the-calling-method-from-the-stack-using-reflection.aspx
    // and /programming/2652460/c-sharp-how-to-get-the-name-of-the-current-method-from-code

    var fn = new methods();

    fn.reflection().Dump("reflection");
    fn.stacktrace().Dump("stacktrace");
    fn.inlineconstant().Dump("inlineconstant");
    fn.constant().Dump("constant");
    fn.expr().Dump("expr");
    fn.exprmember().Dump("exprmember");
    fn.callermember().Dump("callermember");

    new Perf {
        { "reflection", n => fn.reflection() },
        { "stacktrace", n => fn.stacktrace() },
        { "inlineconstant", n => fn.inlineconstant() },
        { "constant", n => fn.constant() },
        { "expr", n => fn.expr() },
        { "exprmember", n => fn.exprmember() },
        { "callermember", n => fn.callermember() },
    }.Vs("Method name retrieval");
}

// Define other methods and classes here
class methods {
    public string reflection() {
        return System.Reflection.MethodBase.GetCurrentMethod().Name;
    }
    public string stacktrace() {
        return new StackTrace().GetFrame(0).GetMethod().Name;
    }
    public string inlineconstant() {
        return "inlineconstant";
    }
    const string CONSTANT_NAME = "constant";
    public string constant() {
        return CONSTANT_NAME;
    }
    public string expr() {
        Expression<Func<methods, string>> ex = e => e.expr();
        return ex.ToString();
    }
    public string exprmember() {
        return expressionName<methods,string>(e => e.exprmember);
    }
    protected string expressionName<T,P>(Expression<Func<T,Func<P>>> action) {
        // https://stackoverflow.com/a/9015598/1037948
        return ((((action.Body as UnaryExpression).Operand as MethodCallExpression).Object as ConstantExpression).Value as MethodInfo).Name;
    }
    public string callermember([CallerMemberName]string name = null) {
        return name;
    }
}

HASIL

refleksi refleksi

stacktrace stacktrace

inlineconstant inlineconstant

konstan konstan

expr e => e.expr ()

exprmember exprmember

callermember Main

Method name retrieval: (reflection) vs (stacktrace) vs (inlineconstant) vs (constant) vs (expr) vs (exprmember) vs (callermember) 

 154673 ticks elapsed ( 15.4673 ms) - reflection
2588601 ticks elapsed (258.8601 ms) - stacktrace
   1985 ticks elapsed (  0.1985 ms) - inlineconstant
   1385 ticks elapsed (  0.1385 ms) - constant
1366706 ticks elapsed (136.6706 ms) - expr
 775160 ticks elapsed ( 77.516  ms) - exprmember
   2073 ticks elapsed (  0.2073 ms) - callermember


>> winner: constant

Perhatikan bahwa exprdan callermembermetode tidak "benar". Dan di sana Anda melihat pengulangan dari komentar terkait bahwa refleksi ~ 15x lebih cepat dari stacktrace.

drzaus
sumber
9

EDIT: MethodBase mungkin merupakan cara yang lebih baik untuk mendapatkan metode yang Anda gunakan (sebagai lawan dari keseluruhan tumpukan panggilan). Saya masih khawatir tentang inlining.

Anda dapat menggunakan StackTrace dalam metode:

StackTrace st = new StackTrace(true);

Dan lihat pada bingkai:

// The first frame will be the method you want (However, see caution below)
st.GetFrames();

Namun, ketahuilah bahwa jika metode ini sebaris, Anda tidak akan berada di dalam metode yang Anda pikirkan. Anda bisa menggunakan atribut untuk mencegah inlining:

[MethodImpl(MethodImplOptions.NoInlining)]
denis phillips
sumber
Sejalan dengan optimasi Rilis terutama rumit karena kode akan berperilaku berbeda dalam konfigurasi Debug dan Rilis. Berhati-hatilah dengan properti kecil, mereka kemungkinan besar adalah korban dari ini.
DK.
Saya bertanya-tanya mengapa Anda akan menggunakan new StackTrace(true)bukan new StackTrace(false). Menyetel ke mana trueakan menyebabkan tumpukan jejak untuk mencoba menangkap nama file, nomor baris dan lain-lain, yang mungkin membuat panggilan ini lebih lambat. Jika tidak, jawaban yang bagus
Ivaylo Slavov
6

Cara sederhana untuk bertransaksi adalah:

System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name;

Jika System.Reflection termasuk dalam blok using:

MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name;
Hiu
sumber
4

Bagaimana dengan 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
0

Coba ini...

    /// <summary>
    /// Return the full name of method
    /// </summary>
    /// <param name="obj">Class that calls this method (use Report(this))</param>
    /// <returns></returns>
    public string Report(object obj)
    {
        var reflectedType = new StackTrace().GetFrame(1).GetMethod().ReflectedType;
        if (reflectedType == null) return null;

        var i = reflectedType.FullName;
        var ii = new StackTrace().GetFrame(1).GetMethod().Name;

        return string.Concat(i, ".", ii);
    }
Adriano silva ribeiro
sumber
0

Saya hanya melakukan ini dengan kelas statis sederhana:

using System.Runtime.CompilerServices;
.
.
.
    public static class MyMethodName
        {
            public static string Show([CallerMemberName] string name = "")
            {
                return name;
            }
        }

lalu dalam kode Anda:

private void button1_Click(object sender, EventArgs e)
        {
            textBox1.Text = MyMethodName.Show();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.Text = MyMethodName.Show();
        }
michael kosak
sumber
-1
new StackTrace().ToString().Split("\r\n",StringSplitOptions.RemoveEmptyEntries)[0].Replace("at ","").Trim()
Baglay Vyacheslav
sumber