Làm thế nào tôi có thể tìm thấy phương thức được gọi là phương thức hiện tại?


503

Khi đăng nhập vào C #, làm thế nào tôi có thể tìm hiểu tên của phương thức được gọi là phương thức hiện tại? Tôi biết tất cả về System.Reflection.MethodBase.GetCurrentMethod(), nhưng tôi muốn đi một bước bên dưới này trong dấu vết ngăn xếp. Tôi đã xem xét phân tích theo dõi ngăn xếp, nhưng tôi hy vọng tìm thấy một cách rõ ràng hơn, một cái gì đó giống như Assembly.GetCallingAssembly()nhưng cho các phương thức.


22
Nếu bạn đang sử dụng .net 4.5 beta +, bạn có thể sử dụng API CallerInform .
Rohit Sharma

5
Thông tin người gọi cũng nhanh hơn
dove

4
Tôi tạo ra một cách nhanh chóng BenchmarkDotNet benchmark trong ba phương pháp chính ( StackTrace, StackFrameCallerMemberName) và gửi kết quả như một ý chính cho người khác nhìn thấy ở đây: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Câu trả lời:


513

Thử cái này:

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

lót:

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

Đó là từ Phương thức Nhận cuộc gọi bằng Reflection [C #] .


12
Bạn cũng có thể tạo chỉ khung bạn cần, thay vì toàn bộ ngăn xếp:
Joel Coehoorn

187
StackFrame mới (1) .GetMethod (). Tên;
Joel Coehoorn

12
Điều này không hoàn toàn đáng tin cậy mặc dù. Hãy xem nếu điều này hoạt động trong một bình luận! Hãy thử làm như sau trong một ứng dụng giao diện điều khiển và bạn thấy rằng các tùy chọn trình biên dịch phá vỡ nó. static void Main (chuỗi [] args) {CallIt (); } void static void CallIt () {Final (); } void void Final () {StackTrace track = new StackTrace (); Khung StackFrame = dấu vết.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Name); }
BlackWasp

10
Điều này không hoạt động khi trình biên dịch nội tuyến hoặc gọi đuôi tối ưu hóa phương thức, trong trường hợp đó ngăn xếp được thu gọn và bạn sẽ tìm thấy các giá trị khác ngoài mong đợi. Khi bạn chỉ sử dụng điều này trong các bản dựng Debug, nó sẽ hoạt động tốt.
Abel

46
Những gì tôi đã làm trong quá khứ là thêm thuộc tính trình biên dịch [Phương thứcImplAttribution (Phương thứcImplOptions.NoInlining)] trước phương thức sẽ tìm kiếm dấu vết ngăn xếp. Điều đó đảm bảo rằng trình biên dịch sẽ không nối tiếp phương thức và theo dõi ngăn xếp sẽ chứa phương thức gọi thực sự (tôi không lo lắng về đệ quy đuôi trong hầu hết các trường hợp.)
Jordan Rieger

363

Trong C # 5, bạn có thể lấy thông tin đó bằng thông tin người gọi :

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

Bạn cũng có thể có được [CallerFilePath][CallerLineNumber].


13
Xin chào, không phải C # 5, nó có sẵn trong 4.5.
Thu hút

35
Các phiên bản @AFract Language (C #) không giống với phiên bản .NET.
kwesolowski

6
@stuartd Hình như [CallerTypeName]đã bị loại khỏi khung .Net hiện tại (4.6.2) và Core CLR
Ph0en1x

4
@ Ph0en1x nó không bao giờ nằm ​​trong khung, quan điểm của tôi là nó sẽ hữu ích nếu có, ví dụ: làm thế nào để có được Tên loại của Người gọi
stuartd

3
@DiegoDeberdt - Tôi đã đọc rằng việc sử dụng điều này không có nhược điểm phản ánh vì nó thực hiện tất cả các công việc tại thời gian biên dịch. Tôi tin rằng nó chính xác như những gì được gọi là phương pháp.
cchamberlain

109

Bạn có thể sử dụng Thông tin người gọi và các tham số tùy chọn:

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

Thử nghiệm này minh họa điều này:

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

Mặc dù StackTrace hoạt động khá nhanh ở trên và sẽ không phải là vấn đề về hiệu suất trong hầu hết các trường hợp, Thông tin người gọi vẫn nhanh hơn nhiều. Trong một mẫu gồm 1000 lần lặp, tôi đã xem nó nhanh hơn 40 lần.


Chỉ khả dụng từ .Net 4.5 mặc dù
DerApe 6/07/2015

1
Lưu ý rằng điều này không hoạt động, nếu người gọi vượt qua một agrument: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"CallerMemberNamechỉ thay thế giá trị mặc định.
Olivier Jacot-Descombes

@ OlivierJacot-Descombes không hoạt động theo cách đó giống như cách một phương thức mở rộng sẽ không hoạt động nếu bạn truyền tham số cho nó. bạn có thể mặc dù một tham số chuỗi khác có thể được sử dụng. Cũng lưu ý rằng việc chia sẻ lại sẽ đưa ra cảnh báo nếu bạn cố gắng thông qua một đối số như bạn đã làm.
bồ câu

1
@dove bạn có thể chuyển bất kỳ thistham số rõ ràng nào vào một phương thức mở rộng. Ngoài ra, Olivier là chính xác, bạn có thể vượt qua một giá trị và [CallerMemberName]không được áp dụng; thay vào đó, nó hoạt động như một ghi đè trong đó giá trị mặc định thường được sử dụng. Như một vấn đề thực tế, nếu chúng ta nhìn vào IL, chúng ta có thể thấy rằng phương thức kết quả không khác gì so với những gì thông thường được phát ra cho một cuộc tranh luận [opt], CallerMemberNamedo đó việc tiêm thuốc là do hành vi CLR. Cuối cùng, các tài liệu: "Thuộc tính Thông tin người gọi [...] ảnh hưởng đến giá trị mặc định được đưa vào khi bỏ qua đối số "
Shaun Wilson

2
Điều này là hoàn hảo và asyncthân thiện StackFramesẽ không giúp bạn với. Cũng không ảnh hưởng đến việc được gọi từ lambda.
Aaron

65

Một bản tóm tắt nhanh chóng của 2 cách tiếp cận với so sánh tốc độ là phần quan trọng.

http://geekswithbloss.net/BlackRmusCoder/archive/2013/07/25/c.net-little-wonders-getting-caller-inif.aspx

Xác định người gọi vào thời gian biên dịch

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

Xác định người gọi bằng ngăn xếp

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

So sánh 2 cách tiếp cận

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

Vì vậy, bạn thấy, sử dụng các thuộc tính là nhiều, nhanh hơn nhiều! Thực tế nhanh hơn gần 25 lần.


Phương pháp này dường như là cách tiếp cận vượt trội. Nó cũng hoạt động trong Xamarin mà không có vấn đề về không gian tên.
lyndon hughey

63

Chúng tôi có thể cải thiện mã của Mr Assad (câu trả lời được chấp nhận hiện tại) chỉ một chút bằng cách khởi tạo chỉ khung mà chúng tôi thực sự cần chứ không phải toàn bộ ngăn xếp:

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

Điều này có thể thực hiện tốt hơn một chút, mặc dù trong nhiều khả năng nó vẫn phải sử dụng toàn bộ ngăn xếp để tạo khung đơn đó. Ngoài ra, nó vẫn có những cảnh báo tương tự mà Alex Lyman đã chỉ ra (trình tối ưu hóa / mã gốc có thể làm hỏng kết quả). Cuối cùng, bạn có thể muốn kiểm tra để chắc chắn rằng new StackFrame(1)hoặc .GetFrame(1)không quay lạinull , vì không chắc là khả năng đó có vẻ như.

Xem câu hỏi liên quan này: Bạn có thể sử dụng sự phản chiếu để tìm tên của phương thức hiện đang thực hiện không?


1
nó thậm chí có thể new ClassName(…)bằng với null?
Tên hiển thị

1
Điều tuyệt vời là nó cũng hoạt động trong .NET Standard 2.0.
srsedate

60

Nói chung, bạn có thể sử dụng System.Diagnostics.StackTracelớp để lấy a System.Diagnostics.StackFrame, và sau đó sử dụng GetMethod()phương thức để lấy một System.Reflection.MethodBaseđối tượng. Tuy nhiên, có một số cảnh báo cho phương pháp này:

  1. Nó đại diện cho ngăn xếp thời gian chạy - tối ưu hóa có thể nội tuyến một phương thức và bạn sẽ không thấy phương thức đó trong theo dõi ngăn xếp.
  2. Nó sẽ không hiển thị bất kỳ khung riêng nào, vì vậy thậm chí nếu có khả năng phương thức của bạn được gọi bằng phương thức gốc, thì điều này sẽ không hoạt động và thực tế không có cách nào hiện có để thực hiện.

( LƯU Ý: Tôi chỉ đang mở rộng trên câu trả lời do Firas Assad cung cấp .)


2
Trong chế độ gỡ lỗi đã tắt tối ưu hóa, bạn có thể xem phương thức nào trong theo dõi ngăn xếp không?
Tấn

1
@AttackingHobo: Có - trừ khi phương thức được nội tuyến (tối ưu hóa trên) hoặc khung gốc, bạn sẽ thấy nó.
Alex Lyman

38

Kể từ .NET 4.5, bạn có thể sử dụng các thuộc tính thông tin người gọi :

  • CallerFilePath - Tệp nguồn được gọi là hàm;
  • CallerLineNumber - Dòng mã được gọi là hàm;
  • CallerMemberName - Thành viên được gọi là hàm.

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

 

Cơ sở này cũng có mặt trong ".NET Core" và ".NET Standard".

Người giới thiệu

  1. Microsoft - Thông tin người gọi (C #)
  2. Microsoft - CallerFilePathAttributeLớp
  3. Microsoft - CallerLineNumberAttributeLớp
  4. Microsoft - CallerMemberNameAttributeLớp

15

Lưu ý rằng làm như vậy sẽ không đáng tin cậy trong mã phát hành, do tối ưu hóa. Ngoài ra, chạy ứng dụng ở chế độ hộp cát (chia sẻ mạng) sẽ không cho phép bạn lấy khung ngăn xếp.

Xem xét lập trình hướng theo khía cạnh (AOP), như PostSharp , thay vì được gọi từ mã của bạn, sửa đổi mã của bạn và do đó biết vị trí của nó mọi lúc.


Bạn hoàn toàn chính xác rằng điều này sẽ không hoạt động trong bản phát hành. Tôi không chắc là tôi thích ý tưởng tiêm mã, nhưng tôi đoán theo nghĩa nào đó, một câu lệnh gỡ lỗi yêu cầu sửa đổi mã, nhưng vẫn vậy. Tại sao không quay lại C macro? Đó là ít nhất một cái gì đó bạn có thể nhìn thấy.
ebyrob

9

Rõ ràng đây là một câu trả lời muộn, nhưng tôi có một lựa chọn tốt hơn nếu bạn có thể sử dụng .NET 4.5 trở lên:

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

Điều này sẽ in Ngày và Giờ hiện tại, theo sau là "Namespace.ClassName.MethodName" và kết thúc bằng ": text".
Đầu ra mẫu:

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

Sử dụng mẫu:

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

Rất tiếc, tôi nên đã giải thích thông số "Phương pháp sau" tốt hơn một chút. Vì vậy, nếu bạn đang gọi phương thức này trong hàm loại "nhật ký", bạn sẽ muốn có được phương thức ngay sau chức năng "nhật ký". vì vậy bạn sẽ gọi GetCallingMethod ("log"). -Cheers
Flanders

6

Có thể bạn đang tìm kiếm một cái gì đó như thế này:

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

Một lớp học tuyệt vời ở đây: http://www.csharp411.com/c-get-calling-method/


StackFrame không đáng tin cậy. Đi lên "2 khung" có thể dễ dàng quay lại các cuộc gọi phương thức.
dùng2864740

2

Một cách tiếp cận khác mà tôi đã sử dụng là thêm một tham số cho phương thức đang đề cập. Ví dụ, thay vì void Foo(), sử dụng void Foo(string context). Sau đó chuyển vào một số chuỗi duy nhất cho biết bối cảnh gọi.

Nếu bạn chỉ cần người gọi / bối cảnh để phát triển, bạn có thể loại bỏ paramtrước khi vận chuyển.


2

Để nhận Tên phương thức và Tên lớp, hãy thử điều này:

    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;

Sẽ là đủ, tôi nghĩ.



1

Chúng tôi cũng có thể sử dụng lambda để tìm người gọi.

Giả sử bạn có một phương thức do bạn định nghĩa:

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

và bạn muốn tìm người gọi nó.

1 . Thay đổi chữ ký phương thức để chúng ta có một tham số kiểu Action (Func cũng sẽ hoạt động):

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

2 . Tên Lambda không được tạo ngẫu nhiên. Quy tắc dường như là:> <CallerMethodName> __ X trong đó CallerMethodName được thay thế bởi hàm trước đó và X là một chỉ mục.

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

3 . Khi chúng ta gọi Phương thứcA, tham số Action / Func phải được tạo bởi phương thức người gọi. Thí dụ:

MethodA(() => {});

4 . Bên trong Phương thứcA bây giờ chúng ta có thể gọi hàm trợ giúp được xác định ở trên và tìm Phương thức của phương thức người gọi.

Thí dụ:

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

0

Thông tin thêm cho câu trả lời của Firas Assaad.

Tôi đã sử dụng new StackFrame(1).GetMethod().Name;trong lõi .net 2.1 với tính năng tiêm phụ thuộc và tôi nhận được phương thức gọi là 'Bắt ​​đầu'.

Tôi đã thử với [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" và nó cho tôi phương thức gọi đúng


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

1
Tôi không downvote, nhưng muốn lưu ý rằng việc thêm một số văn bản để giải thích lý do tại sao bạn đăng thông tin rất giống nhau (năm sau) có thể làm tăng giá trị của câu hỏi và tránh tiếp tục hạ thấp.
Shaun Wilson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.