ฉันจะค้นหาวิธีที่เรียกว่าวิธีการปัจจุบันได้อย่างไร


503

เมื่อเข้าสู่ระบบใน C # ฉันจะเรียนรู้ชื่อของวิธีการที่เรียกว่าวิธีการในปัจจุบันได้อย่างไร ฉันรู้ทุกอย่างเกี่ยวกับSystem.Reflection.MethodBase.GetCurrentMethod()แต่ฉันต้องการไปหนึ่งขั้นตอนด้านล่างนี้ในกองติดตาม ฉันได้พิจารณาการแยกการติดตามสแต็ก แต่ฉันหวังว่าจะค้นหาวิธีที่ชัดเจนยิ่งขึ้นซึ่งเป็นวิธีที่ชัดเจนAssembly.GetCallingAssembly()กว่า


22
หากคุณกำลังใช้ .net 4.5 เบต้า + คุณสามารถใช้CallerInformation API
Rohit Sharma

5
ข้อมูลการโทรยังเป็นมากได้เร็วขึ้น
นกพิราบ

4
ฉันสร้างอย่างรวดเร็วBenchmarkDotNetมาตรฐานของทั้งสามวิธีหลัก ( StackTrace, StackFrameและCallerMemberName) และโพสต์ผลเป็นส่วนสำคัญสำหรับการให้คนอื่นเห็นที่นี่: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
ฌอนวิลสัน

คำตอบ:


512

ลองสิ่งนี้:

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

หนึ่งในสายการบิน:

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

มันมาจากการรับใช้เรียกวิธีการสะท้อน [C #]


12
นอกจากนี้คุณยังสามารถสร้างเฟรมที่คุณต้องการแทนที่จะเป็นกองซ้อนทั้งหมด:
Joel Coehoorn

187
ใหม่ StackFrame (1) .GetMethod (). ชื่อ;
Joel Coehoorn

12
สิ่งนี้ไม่น่าเชื่อถืออย่างสิ้นเชิง เรามาดูกันว่าการทำงานในความคิดเห็น! ลองสิ่งต่อไปนี้ในแอปพลิเคชันคอนโซลและคุณเห็นว่าตัวเลือกคอมไพเลอร์ทำลาย โมฆะคงที่หลัก (สตริง [] args) {CallIt (); } ส่วนตัวโมฆะคง CallIt () {สุดท้าย (); } คงโมฆะสุดท้าย () {ติดตาม StackTrace = ใหม่ StackTrace (); StackFrame frame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). ชื่อ); }
BlackWasp

10
วิธีนี้ใช้ไม่ได้เมื่อคอมไพเลอร์ inlines หรือ tail-call ปรับวิธีการให้เหมาะสมซึ่งในกรณีนี้ stack จะถูกยุบและคุณจะพบค่าอื่น ๆ เมื่อคุณใช้สิ่งนี้ในการสร้าง Debug จะทำงานได้ดี
Abel

46
สิ่งที่ฉันทำในอดีตคือเพิ่มแอตทริบิวต์คอมไพเลอร์[MethodImplAttribute (MethodImplOptions.NoInlining)]ก่อนหน้าวิธีที่จะค้นหาการติดตามสแต็ก ที่ทำให้มั่นใจว่าคอมไพเลอร์จะไม่ได้อยู่ในวิธีเส้นและกองติดตามจะมีวิธีการเรียกร้องอันแท้จริง (ผมไม่ได้กังวลเกี่ยวกับหาง recursion ในกรณีส่วนใหญ่.)
จอร์แดน Rieger

363

ใน C # 5 คุณสามารถรับข้อมูลนั้นได้โดยใช้ข้อมูลผู้โทร :

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

นอกจากนี้คุณยังจะได้รับและ[CallerFilePath][CallerLineNumber]


13
สวัสดีไม่ใช่ C # 5 มีให้ใน 4.5
หยุด

35
รุ่น @ AFract Language (C #) ไม่เหมือนกับรุ่น. NET
kwesolowski

6
@ Stuartd ดูเหมือนว่า[CallerTypeName]ถูกทิ้งไว้จากกรอบงาน. Net ปัจจุบัน (4.6.2) และ Core CLR
Ph0en1x

4
@ Ph0en1x ไม่เคยอยู่ในกรอบประเด็นของฉันคือมันจะเป็นประโยชน์ถ้ามันเป็นเช่นวิธีการได้รับชื่อประเภทของ CallerMember
stuartd

3
@DiegoDeberdt - ฉันได้อ่านว่าการใช้สิ่งนี้ไม่มีข้อเสียในการสะท้อนเนื่องจากมันทำงานทั้งหมดในเวลาคอมไพล์ ฉันเชื่อว่ามันถูกต้องเป็นสิ่งที่เรียกว่าวิธีการ
cchamberlain

109

คุณสามารถใช้ข้อมูลผู้โทรและพารามิเตอร์เพิ่มเติม:

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

การทดสอบนี้แสดงให้เห็นถึงสิ่งนี้:

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

ในขณะที่ StackTrace ทำงานได้ค่อนข้างเร็วด้านบนและจะไม่เป็นปัญหาด้านประสิทธิภาพในกรณีส่วนใหญ่ข้อมูลผู้โทรยังคงเร็วขึ้นมาก ในตัวอย่างการวนซ้ำ 1,000 ครั้งฉันโอเวอร์คล็อกมันเร็วขึ้น 40 เท่า


ใช้ได้ตั้งแต่. Net 4.5 เท่านั้น
DerApe

1
โปรดทราบว่าวิธีนี้ใช้ไม่ได้หากผู้เรียกผ่าน agrument: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"เนื่องจากตัวเลือกCallerMemberNameนี้จะแทนที่ค่าเริ่มต้นเท่านั้น
Olivier Jacot-Descombes

@ OlivierJacot-Descombes ไม่ทำงานในลักษณะเดียวกันวิธีการส่วนขยายจะไม่ทำงานหากคุณส่งพารามิเตอร์ให้ คุณสามารถแม้ว่าพารามิเตอร์สตริงอื่นซึ่งสามารถนำมาใช้ โปรดทราบด้วยว่าผู้ซ่อมแซมจะให้คำเตือนแก่คุณหากคุณพยายามส่งผ่านการโต้แย้งเช่นเดียวกับคุณ
นกพิราบ

1
@dove คุณสามารถส่งthisพารามิเตอร์ที่ชัดเจนใด ๆไปยังวิธีการขยาย นอกจากนี้ Olivier นั้นถูกต้องคุณสามารถส่งผ่านค่าและ[CallerMemberName]ไม่ได้ใช้ แต่จะทำหน้าที่เป็นการแทนที่โดยปกติจะใช้ค่าเริ่มต้นแทน ตามความเป็นจริงถ้าเราดู IL เราจะเห็นว่าวิธีการที่เกิดขึ้นนั้นไม่ต่างไปจากสิ่งที่ปกติจะถูกปล่อยออกมาเพื่อ[opt]หาเรื่องการฉีดCallerMemberNameจึงเป็นพฤติกรรม CLR ท้ายที่สุดเอกสาร: "แอตทริบิวต์ข้อมูลผู้โทร [... ] ส่งผลต่อค่าเริ่มต้นที่ส่งผ่านเมื่อไม่มีการโต้แย้ง "
Shaun Wilson

2
มันสมบูรณ์แบบและเป็นasyncมิตรซึ่งStackFrameจะไม่ช่วยคุณ ยังไม่ส่งผลกระทบต่อการถูกเรียกจากแลมบ์ดา
แอรอน

65

การสรุปอย่างย่อของทั้ง 2 วิธีด้วยการเปรียบเทียบความเร็วเป็นส่วนสำคัญ

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

การกำหนดผู้โทร ณ เวลารวบรวม

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);
}

การกำหนดผู้โทรโดยใช้สแต็ก

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);
}

เปรียบเทียบ 2 แนวทาง

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

ดังนั้นคุณจะเห็นว่าการใช้คุณสมบัตินั้นเร็วกว่ามาก! เกือบเร็วขึ้น 25 เท่าในความเป็นจริง


วิธีนี้ดูเหมือนจะเป็นวิธีที่เหนือกว่า นอกจากนี้ยังทำงานใน Xamarin โดยไม่มีปัญหาของเนมสเปซที่ไม่สามารถใช้ได้
lyndon hughey

63

เราสามารถปรับปรุงโค้ดของ Mr Assad (คำตอบที่ได้รับการยอมรับในปัจจุบัน) เพียงเล็กน้อยโดย instantiating เฉพาะเฟรมที่เราต้องการจริงมากกว่าสแต็คทั้งหมด:

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

สิ่งนี้อาจทำงานได้ดีขึ้นเล็กน้อยแม้ว่าในทุกโอกาสมันยังคงต้องใช้สแต็คแบบเต็มเพื่อสร้างเฟรมเดี่ยวนั้น นอกจากนี้ยังมีข้อแม้เดียวกันกับที่ Alex Lyman ชี้ให้เห็น (ตัวเพิ่มประสิทธิภาพ / โค้ดเนมอาจทำให้ผลลัพธ์เสียหาย) ในที่สุดคุณอาจต้องการตรวจสอบเพื่อให้แน่ใจว่าnew StackFrame(1)หรือ.GetFrame(1)ไม่ส่งคืนnullเพราะอาจเป็นไปได้

ดูคำถามที่เกี่ยวข้อง: คุณสามารถใช้การสะท้อนเพื่อหาชื่อของวิธีการดำเนินการในปัจจุบันได้หรือไม่?


1
เป็นไปได้ไหมที่new ClassName(…)เท่ากับโมฆะ?
ชื่อที่ปรากฏ

1
สิ่งที่ดีคือสิ่งนี้ยังใช้งานได้ใน. NET Standard 2.0
srsedate

60

โดยทั่วไปคุณสามารถใช้System.Diagnostics.StackTraceคลาสเพื่อรับ a System.Diagnostics.StackFrameจากนั้นใช้GetMethod()วิธีการรับSystem.Reflection.MethodBaseวัตถุ อย่างไรก็ตามมีคำเตือนบางประการสำหรับวิธีนี้:

  1. เพราะมันหมายถึงสแต็กรันไทม์ - การเพิ่มประสิทธิภาพสามารถอินไลน์วิธีและคุณจะไม่เห็นวิธีการนั้นในการติดตามสแต็ก
  2. มันจะไม่แสดงเฟรมดั้งเดิมใด ๆ ดังนั้นหากมีโอกาสที่วิธีการของคุณจะถูกเรียกใช้โดยวิธีเนทีฟวิธีนี้จะไม่ทำงานและในความเป็นจริงไม่มีวิธีที่ใช้ได้ในปัจจุบัน

( หมายเหตุ: ฉันเพิ่งขยายคำตอบของ Firas Assad )


2
ในโหมดดีบักที่ปิดการปรับให้เหมาะสมคุณจะสามารถดูว่าวิธีการนั้นอยู่ในการติดตามสแต็กหรือไม่
AttackHobo

1
@AttackingHobo: ใช่ - ยกเว้นว่าวิธีการแบบอินไลน์ (การเพิ่มประสิทธิภาพ) หรือเฟรมพื้นเมืองคุณจะเห็นมัน
Alex Lyman

38

ในฐานะ. NET 4.5 คุณสามารถใช้คุณสมบัติข้อมูลผู้โทรได้ :

  • CallerFilePath - ไฟล์ต้นฉบับที่เรียกว่าฟังก์ชั่น;
  • CallerLineNumber - สายของรหัสที่เรียกว่าฟังก์ชั่น;
  • CallerMemberName - สมาชิกที่เรียกว่าฟังก์ชั่น

    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);
    }

 

สิ่งอำนวยความสะดวกนี้มีอยู่ใน ". NET Core" และ ". NET Standard"

อ้างอิง

  1. Microsoft - ข้อมูลผู้โทร (C #)
  2. Microsoft - CallerFilePathAttributeคลาส
  3. Microsoft - CallerLineNumberAttributeคลาส
  4. Microsoft - CallerMemberNameAttributeคลาส

15

โปรดทราบว่าการทำเช่นนั้นจะไม่น่าเชื่อถือในรหัสการเผยแพร่เนื่องจากการปรับให้เหมาะสม นอกจากนี้การรันแอปพลิเคชันในโหมดแซนด์บ็อกซ์ (เครือข่ายแชร์) จะไม่อนุญาตให้คุณคว้าเฟรมสแต็กเลย

พิจารณาการเขียนโปรแกรมแบบมุ่งเน้นด้าน (AOP) เช่นPostSharpซึ่งแทนที่จะถูกเรียกจากรหัสของคุณปรับเปลี่ยนรหัสของคุณและทำให้รู้ว่าอยู่ที่ไหนตลอดเวลา


คุณถูกต้องอย่างแน่นอนว่าสิ่งนี้จะไม่ทำงานในการเปิดตัว ฉันไม่แน่ใจว่าฉันชอบแนวคิดของการฉีดโค้ด แต่ฉันเดาว่าคำสั่งดีบั๊กต้องการการแก้ไขโค้ด แต่ก็ยัง ทำไมไม่กลับไปที่มาโคร C อย่างน้อยก็เป็นสิ่งที่คุณเห็น
ebyrob

9

เห็นได้ชัดว่านี่เป็นคำตอบที่ล่าช้า แต่ฉันมีตัวเลือกที่ดีกว่าถ้าคุณสามารถใช้. NET 4.5 หรือมากกว่า:

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

สิ่งนี้จะพิมพ์วันที่และเวลาปัจจุบันตามด้วย "Namespace.ClassName.MethodName" และลงท้ายด้วย ": text"
ตัวอย่างผลลัพธ์:

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

ตัวอย่างการใช้งาน:

Logger.WriteInformation<MainWindow>("MainWindow initialized");

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;
}

อุ๊ปส์ฉันควรจะอธิบาย "MethodAfter" ดีกว่าเล็กน้อย ดังนั้นหากคุณกำลังเรียกวิธีนี้ในฟังก์ชั่นประเภท "บันทึก" คุณจะต้องได้รับวิธีการหลังจากฟังก์ชั่น "บันทึก" ดังนั้นคุณจะเรียก GetCallingMethod ("log") -Cheers
Flanders

6

บางทีคุณกำลังมองหาสิ่งนี้:

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

4
private static MethodBase GetCallingMethod()
{
  return new StackFrame(2, false).GetMethod();
}

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

ชั้นเรียนที่ยอดเยี่ยมอยู่ที่นี่: http://www.csharp411.com/c-get-calling-method/


StackFrame ไม่น่าเชื่อถือ การขึ้น "2 เฟรม" อาจจะง่ายเกินไปที่จะกลับไปที่วิธีการโทร
user2864740

2

อีกวิธีที่ฉันได้ใช้คือการเพิ่มพารามิเตอร์ให้กับวิธีที่เป็นปัญหา ตัวอย่างเช่นแทนที่จะใช้void Foo() void Foo(string context)จากนั้นส่งผ่านสตริงที่ไม่ซ้ำกันซึ่งระบุถึงบริบทการโทร

หากคุณต้องการเพียงผู้โทร / บริบทเพื่อการพัฒนาคุณสามารถลบparamก่อนที่จะส่ง


2

สำหรับการรับชื่อวิธีและชื่อชั้นลองนี้:

    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 );
    }

1
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;

จะพอฉันคิดว่า



1

เราสามารถใช้แลมบ์ดาเพื่อค้นหาผู้โทร

สมมติว่าคุณมีวิธีที่กำหนดโดยคุณ:

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

และคุณต้องการค้นหามันเป็นผู้โทร

1 . เปลี่ยนลายเซ็นวิธีการเพื่อให้เรามีพารามิเตอร์ประเภท Action (Func จะทำงานด้วย):

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

2 . ชื่อของแลมบ์ดาจะไม่ถูกสร้างแบบสุ่ม กฎน่าจะเป็น:> <CallerMethodName> __X โดยที่ CallerMethodName ถูกแทนที่ด้วยฟังก์ชันก่อนหน้าและ X คือดัชนี

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

3 . เมื่อเราเรียก MethodA พารามิเตอร์ Action / Func จะต้องถูกสร้างขึ้นโดยวิธีการของผู้โทร ตัวอย่าง:

MethodA(() => {});

4 . ภายใน MethodA ตอนนี้เราสามารถเรียกใช้ฟังก์ชันตัวช่วยที่กำหนดไว้ด้านบนแล้วค้นหา MethodInfo ของเมธอด caller

ตัวอย่าง:

MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);

0

ข้อมูลเพิ่มเติมเกี่ยวกับคำตอบของ Firas Assaad

ฉันใช้new StackFrame(1).GetMethod().Name;ใน. net core 2.1 พร้อมการฉีดแบบพึ่งพาและฉันได้รับวิธีการโทรเป็น 'เริ่ม'

ฉันลองด้วย[System.Runtime.CompilerServices.CallerMemberName] string callerName = "" และมันทำให้ฉันมีวิธีการโทรที่ถูกต้อง


-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;

1
ฉันไม่ได้ลงคะแนน แต่ต้องการที่จะทราบว่าการเพิ่มข้อความเพื่ออธิบายว่าทำไมคุณโพสต์ข้อมูลที่คล้ายกันมาก (ปีต่อมา) อาจเพิ่มมูลค่าของคำถามและหลีกเลี่ยงการ downvoting เพิ่มเติม
Shaun Wilson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.