Geçerli yöntemi çağıran yöntemi nasıl bulabilirim?


503

C # 'a giriş yaparken, geçerli yöntemi çağıran yöntemin adını nasıl öğrenebilirim? Her şeyi biliyorum System.Reflection.MethodBase.GetCurrentMethod(), ama yığın izinde bunun bir adım altına gitmek istiyorum. Yığın izini ayrıştırmayı düşündüm, ancak daha temiz bir yol bulmayı umuyorum, Assembly.GetCallingAssembly()yöntemler gibi ama.


22
.Net 4.5 beta + kullanıyorsanız, CallerInformation API'sını kullanabilirsiniz .
Rohit Sharma

5
Arayan Bilgisi de çok daha hızlı
güvercin

4
Üç ana yöntemin ( ve ve ) hızlı bir BenchmarkDotNet karşılaştırmasını oluşturdum ve sonuçları başkalarının burada görmesi için bir özet olarak yayınladımStackTraceStackFrameCallerMemberName
Shaun Wilson

Yanıtlar:


512

Bunu dene:

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

tek satırlık:

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

Bu dan al yansıma [C #] kullanarak yöntemi çağırma .


12
Ayrıca, tüm yığın yerine sadece ihtiyacınız olan kareyi oluşturabilirsiniz:
Joel Coehoorn

187
yeni StackFrame (1) .GetMethod (). Name;
Joel Coehoorn

12
Bu tamamen güvenilir değil. Bunun bir yorumda işe yarayıp yaramadığını görelim! Bir konsol uygulamasında aşağıdakileri deneyin ve derleyici optimizasyonlarının onu bozduğunu görürsünüz. statik void Main (dize [] args) {CallIt (); } özel statik geçersiz CallIt () {Final (); } statik void Final () {StackTrace izleme = yeni StackTrace (); StackFrame çerçevesi = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Adı); }
BlackWasp

10
Derleyici satır içi veya kuyruk çağrısı yöntemi en iyi duruma getirdiğinde bu çalışmaz, bu durumda yığın daraltılır ve beklenenden başka değerler bulacaksınız. Bunu yalnızca Debug derlemelerinde kullandığınızda, iyi çalışır.
Abel

46
Ne geçmişte yaptım derleme özniteliği [MethodImplAttribute (MethodImplOptions.NoInlining)] yığın izlemesini arayacak yöntem önce ekleyin . Bu, derleyicinin yöntemi satır içi yapmamasını ve yığın izlemesinin gerçek arama yöntemini içermesini sağlar (çoğu durumda kuyruk özyineleme konusunda endişelenmiyorum.)
Jordan Rieger

363

C # 5'te arayan bilgilerini kullanarak bu bilgileri alabilirsiniz :

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

Ayrıca [CallerFilePath]ve alabilirsiniz [CallerLineNumber].


13
Merhaba, C # 5 değil, 4.5'te mevcut.
15'te

35
@AFract Dili (C #) sürümleri .NET sürümüyle aynı değildir.
kwesolowski

6
@stuartd Görünüşe göre [CallerTypeName]şu anki .Net çerçevesinden (4.6.2) ve Core
CLR'den ayrıldı

4
@ Ph0en1x asla çerçevede değildi, benim açımdan, örneğin nasıl bir CallerMember türü adı almak
stuartd

3
@DiegoDeberdt - Derleme zamanında tüm işleri yaptığı için bunu kullanmanın hiçbir olumsuz yanı olmadığını okudum. Metod denilen şeyin doğru olduğuna inanıyorum.
cchamberlain

109

Arayan Bilgilerini ve isteğe bağlı parametreleri kullanabilirsiniz:

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

Bu test bunu göstermektedir:

[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 yukarıda oldukça hızlı çalışmasına rağmen, çoğu durumda Arayan Bilgileri çok daha hızlıdır. 1000 yinelemeden oluşan bir örnekte, bunu 40 kat daha hızlı izledim.


Yine de yalnızca .Net
4.5'ten edinilebilir

1
Çağıran bir varsayılanı geçerse, bunun işe yaramadığına dikkat edin: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"çünkü CallerMemberNameyalnızca varsayılan değer yerine geçer .
Olivier Jacot-Descombes

@ OlivierJacot-Descombes, bu şekilde çalışmazsa, bir parametreyi iletirseniz bir genişletme yöntemi çalışmaz. kullanılabilir başka bir dize parametresi olsa. Ayrıca, sizin gibi bir tartışmayı geçmeye çalıştığınızda yeniden paylaşıcının size bir uyarı vereceğini unutmayın.
güvercin

1
@ güvercin herhangi bir açık thisparametreyi bir uzantı yöntemine geçirebilirsiniz . Ayrıca, Olivier doğrudur, bir değer iletebilirsiniz ve [CallerMemberName]uygulanmaz; bunun yerine varsayılan değerin normalde kullanıldığı bir geçersiz kılma işlevi görür. Nitekim, IL'ye bakarsak, ortaya çıkan yöntemin, bir [opt]arg için normalde yayılacak olandan farklı olmadığını görebiliriz CallerMemberName, bu nedenle enjeksiyonu bir CLR davranışıdır. Son olarak, dokümanlar: "Arayan Bilgisi öznitelikleri [...] , argüman atlandığında geçirilen varsayılan değeri etkiler "
Shaun Wilson

2
Bu mükemmel ve size yardımcı olmayacak asyncdostudur StackFrame. Ayrıca lambdadan çağrılmayı da etkilemez.
Aaron

65

Hız yaklaşımının önemli bir parçası olduğu 2 yaklaşımın hızlı bir özeti.

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

Arayanın derleme zamanında belirlenmesi

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

Yığını kullanarak arayanı belirleme

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 yaklaşımın karşılaştırılması

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

Gördüğünüz gibi, öznitelikleri kullanmak çok daha hızlı! Aslında yaklaşık 25 kat daha hızlı.


Bu yöntem üstün bir yaklaşım gibi görünmektedir. Ayrıca Xamarin'de, ad alanı sorunu olmadan çalışır.
lyndon hughey

63

Bay Assad'ın kodunu (şu anki kabul edilen cevap), tüm yığının yerine sadece ihtiyacımız olan çerçeveyi somutlaştırarak biraz geliştirebiliriz:

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

Bu biraz daha iyi performans gösterebilir, ancak her durumda, o tek kareyi oluşturmak için tam yığını kullanması gerekir. Ayrıca, Alex Lyman'ın belirttiği aynı uyarılar hala var (optimize edici / yerel kod sonuçları bozabilir). Son olarak, bu olasılığın göründüğü kadar olası olmadığından emin olmak new StackFrame(1)veya .GetFrame(1)geri dönmemek için kontrol etmek nullisteyebilirsiniz.

Bu ilgili soruya bakın: Şu anda yürütülen yöntemin adını bulmak için yansıma kullanabilir misiniz?


1
o mümkün olduğunu bile new ClassName(…)boş eşittir?
Görünen Ad

1
Güzel olan, bunun .NET Standard 2.0'da da çalışmasıdır.
srsedate

60

Genel olarak, System.Diagnostics.StackTracesınıfı a almak için System.Diagnostics.StackFramekullanabilir ve sonra nesneyi GetMethod()almak için yöntemi kullanabilirsiniz System.Reflection.MethodBase. Bununla birlikte, bu yaklaşımın bazı uyarıları vardır :

  1. Bu temsil çalışma zamanı yığını - optimizasyonlar bir yöntemi satır içi olabilir ve bundan olacak değil yığın izlemesinde bu yöntemi görüyoruz.
  2. Bu olacak değil senin yöntem bir yerli yöntemle çağrılan bir şans, bu irade bile var eğer öyleyse, herhangi bir yerel çerçeveleri göstermek değil iş ve aslında içinde bunu yapmak için mevcut yolu henüz geliştirilememiştir.

( NOT: Sadece Firas Esad'ın verdiği cevabı genişletiyorum .)


2
Optimizasyonların kapalı olduğu hata ayıklama modunda, yığın izlemesinde yöntemin ne olduğunu görebiliyor musunuz?
AttackingHobo

1
@AttackingHobo: Evet - yöntem satır içi değilse (optimizasyonlar açık) veya yerel bir çerçeve yoksa, görürsünüz.
Alex Lyman

38

.NET 4.5'ten itibaren Arayan Bilgisi Özniteliklerini kullanabilirsiniz :

  • CallerFilePath - İşlevi çağıran kaynak dosya;
  • CallerLineNumber - Fonksiyon olarak adlandırılan kod satırı;
  • CallerMemberName - İşlevi çağıran üye.

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

 

Bu özellik ".NET Core" ve ".NET Standard" da da mevcuttur.

Referanslar

  1. Microsoft - Arayan Bilgisi (C #)
  2. Microsoft - CallerFilePathAttributeSınıf
  3. Microsoft - CallerLineNumberAttributeSınıf
  4. Microsoft - CallerMemberNameAttributeSınıf

15

Bunu yapmanın optimizasyon nedeniyle yayın kodunda güvenilir olmayacağını unutmayın. Ayrıca, uygulamayı korumalı alan modunda (ağ paylaşımı) çalıştırmak yığın çerçevesini yakalamanıza izin vermez.

PostSharp gibi , kodunuzdan çağrılmak yerine kodunuzu değiştiren ve böylece her zaman nerede olduğunu bilen en boy yönelimli programlamayı (AOP) düşünün .


Bunun sürümde işe yaramayacağından kesinlikle haklısınız. Ben kod enjeksiyon fikrini seviyorum emin değilim, ama bir anlamda bir hata ayıklama deyimi kod değişikliği gerektirir, ama yine de sanırım. Neden sadece C makrolarına geri dönmeyelim? En azından görebileceğiniz bir şey.
ebyrob

9

Açıkçası bu geç bir cevaptır, ancak .NET 4.5 veya daha fazlasını kullanabiliyorsanız daha iyi bir seçeneğim var:

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

Bu işlem geçerli Tarih ve Saati, ardından "Namespace.ClassName.MethodName" ve ": text" ile biter.
Örnek çıktı:

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

Örnek kullanım:

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

ayy, "MethodAfter" parametresini biraz daha iyi açıklamalıydım. Dolayısıyla, bu yöntemi bir "log" tipi fonksiyonda çağırıyorsanız, yöntemi "log" fonksiyonundan hemen sonra almak istersiniz. böylece GetCallingMethod ("log") öğesini çağırırsınız. -Cheers
Flanders

6

Belki böyle bir şey arıyorsunuz:

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

Harika bir sınıf burada: http://www.csharp411.com/c-get-calling-method/


StackFrame güvenilir değildir. "2 kare" yukarı çıkmak kolayca yöntem çağrıları geri gidebilir.
user2864740

2

Kullandığım başka bir yaklaşım, söz konusu yönteme bir parametre eklemektir. Örneğin, yerine void Foo(), kullanımıvoid Foo(string context) . Ardından çağıran bağlamı gösteren benzersiz bir dize iletin.

Gelişim için yalnızca arayan / bağlama ihtiyacınız varsa, paramgöndermeden önce kaldırabilirsiniz .


2

Yöntem Adı ve Sınıf Adı almak için şunu deneyin:

    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;

bence yeterli olacak.



1

Arayanı bulmak için lambda'yı da kullanabiliriz.

Sizin tanımladığınız bir yönteminiz olduğunu varsayalım:

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

ve arayanı bulmak istiyorsun.

1 . Eylem türünde bir parametrenin olması için yöntem imzasını değiştirin (Func da çalışır):

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

2 . Lambda adları rastgele oluşturulmaz. Kural şöyledir:> <CallerMethodName> __X Burada CallerMethodName, önceki işlevle değiştirilir ve X, bir dizindir.

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

3 . MethodA'yı çağırdığımızda, Action / Func parametresi, arayan yöntemi tarafından oluşturulmalıdır. Misal:

MethodA(() => {});

4 . Şimdi, yukarıda tanımlanan yardımcı işlevi çağırabilir ve arayan yönteminin MethodInfo'yu bulabiliriz.

Misal:

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

0

Firas Assaad cevabına ek bilgi.

Ben kullandım new StackFrame(1).GetMethod().Name;bağımlılık enjeksiyonu ile .net çekirdek 2.1 ve ben 'Başlat' olarak çağıran yöntemini alıyorum.

Ben denedim [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" ve bana doğru arama yöntemi verir


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

1
Ben aşağı oy vermedi, ancak çok benzer bilgiler (yıl sonra) gönderdi neden açıklamak için bazı metin ekleyerek sorunun değerini artırabilir ve daha fazla downvoting önlemek not etmek istedim.
Shaun Wilson
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.