Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: Add ability to get Caller MethodInfo from any code at runtime #60759

Open
feeleen opened this issue Oct 22, 2021 · 6 comments
Open
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Diagnostics
Milestone

Comments

@feeleen
Copy link

feeleen commented Oct 22, 2021

Background and motivation

Current speс assumes to use CallerMemberName attribute to know caller name.
But this require to write a lot of excessive/garbage code, and do not cover all situations. And this sometimes results in ".ctor" values which is useless.

If we want to know caller name, we don't want see constructors (".ctor") as a result (because it's a useless info), we want to know caller Method name (omitting implicit or explicit constructor calls chain!)

In scenario below the caller member is empty because of implicit constructor calls.

using System;
using System.Runtime.CompilerServices;

class Program
{
    static void Main()
    {
        B b = new();
    }
}

public class B : A {}

public class A
{
    public A([CallerMemberName] string? value = null)
    {
        Console.WriteLine(value);
    }
}

If I have dll with public class A which relies on CallerMemberName attribute in it's constructor - it won't work because I can't control how descendant classes are defined.

I tried this workaround to discover caller member:

public A([CallerMemberName] string? value = null)
{
      new StackTrace().GetFrames().ToList()
                .ForEach(x => Console.WriteLine(x.GetMethod()?.Name))
}

resut is:

.ctor
.ctor
Main

I don't need ".ctor". I look for Main. Ok.

But this works only for sync code. When it comes to async calls - the StackTrace I get doesn't contain a sync caller member ("Main") at all, because the call chain is broken here:

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Foo().GetAwaiter().GetResult();
    }

    static async Task Foo()
    {
        await Task.Delay(100);
        B b = new();
    }
}

public class B : A {}

public class A
{
    public A([CallerMemberName] string? value = null)
    {
        // in this stackTrace we know nothing about Main
        new StackTrace().GetFrames().ToList()
            .ForEach(x => Console.WriteLine(x.GetMethod()?.Name));

        // here we will see Main:
        throw new Exception();
    }
}

console output is:

.ctor
.ctor
MoveNext
ExecutionContextCallback
RunInternal
MoveNext
MoveNext
<OutputWaitEtwEvents>b__12_0
Invoke
RunOrScheduleAction
RunContinuations
FinishContinuations
TrySetResult
CompleteTimedOut
<.ctor>b__1_0
CallCallback
Fire
FireNextTimers
AppDomainTimerCallback

There is no "Main" here. Chain is broken.

but if we look at thrown Exception output - we can see "Program.Main()". chain isn't broken.

   at A..ctor(String value) in H:\My programming\_VS2022\ConsoleApp1\ConsoleApp1\Program.cs:line 35
   at B..ctor()
   at Program.<Foo>d__1.MoveNext() in H:\My programming\_VS2022\ConsoleApp1\ConsoleApp1\Program.cs:line 18
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Program.Main() in C:\Temp\ConsoleApp1\ConsoleApp1\Program.cs:line 12

So,

It would be great if caller name or caller MethodInfo (omit constructors! they are useless) to be stored somewhere in ExecutionContext when it's captured, and to be able to get this info somehow from sync or async code. To get it I want to call CurrentThread.CallerMemberName or something like CurrentThread.GetCallerMethod(). It'll look for callers in current thread (if async code), -> if not found -> Look for it in ExecutionContext

the second Issue here is that new StackTrace() doesn't contain a frames with sync code, from which async code is called.

API Proposal

MethodInfo mi = CurrentThread.GetCallerMethod();
//or
MethodInfo mi = MethodBase.GetCallerMethod();

API Usage

MethodInfo mi = CurrentThread.GetCallerMethod();
//or
MethodInfo mi = MethodBase.GetCallerMethod();

Alternative Designs

No response

Risks

No response

@feeleen feeleen added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Oct 22, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost
Copy link

ghost commented Oct 22, 2021

Tagging subscribers to this area: @buyaa-n
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

Current speс assumes to use CallerMemberName attribute to know caller name.
But this require to write a lot of excessive/garbage code, and do not cover all situations. And this sometimes results in ".ctor" values which is useless.

If we want to know caller name, we don't want see constructors (".ctor") as a result (because it's a useless info), we want to know caller Method name (omitting implicit or explicit constructor calls chain!)

In scenario below the caller member is empty because of implicit constructor calls.

using System;
using System.Runtime.CompilerServices;

class Program
{
    static void Main()
    {
        B b = new();
    }
}

public class B : A {}

public class A
{
    public A([CallerMemberName] string? value = null)
    {
        Console.WriteLine(value);
    }
}

If I have dll with public class A which relies on CallerMemberName attribute in it's constructor - it won't work because I can't control how descendant classes are defined.

I tried this workaround to discover caller member:

public A([CallerMemberName] string? value = null)
{
      new StackTrace().GetFrames().ToList()
                .ForEach(x => Console.WriteLine(x.GetMethod()?.Name))
}

resut is:

.ctor
.ctor
Main

I don't need ".ctor". I look for Main. Ok.

But this works only for sync code. When it comes to async calls - the StackTrace I get doesn't contain a sync caller member ("Main") at all, because the call chain is broken here:

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Foo().GetAwaiter().GetResult();
    }

    static async Task Foo()
    {
        await Task.Delay(100);
        B b = new();
    }
}

public class B : A {}

public class A
{
    public A([CallerMemberName] string? value = null)
    {
        // in this stackTrace we know nothing about Main
        new StackTrace().GetFrames().ToList()
            .ForEach(x => Console.WriteLine(x.GetMethod()?.Name));

        // here we will see Main:
        throw new Exception();
    }
}

console output is:

.ctor
.ctor
MoveNext
ExecutionContextCallback
RunInternal
MoveNext
MoveNext
<OutputWaitEtwEvents>b__12_0
Invoke
RunOrScheduleAction
RunContinuations
FinishContinuations
TrySetResult
CompleteTimedOut
<.ctor>b__1_0
CallCallback
Fire
FireNextTimers
AppDomainTimerCallback

There is no "Main" here. Chain is broken.

but if we look at thrown Exception output - we can see "Program.Main()". chain isn't broken.

   at A..ctor(String value) in H:\My programming\_VS2022\ConsoleApp1\ConsoleApp1\Program.cs:line 35
   at B..ctor()
   at Program.<Foo>d__1.MoveNext() in H:\My programming\_VS2022\ConsoleApp1\ConsoleApp1\Program.cs:line 18
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Program.Main() in C:\Temp\ConsoleApp1\ConsoleApp1\Program.cs:line 12

So,

It would be great if caller name or caller MethodInfo (omit constructors! they are useless) to be stored somewhere in ExecutionContext when it's captured, and to be able to get this info somehow from sync or async code. To get it I want to call CurrentThread.CallerMemberName or something like CurrentThread.GetCallerMethod(). It'll look for callers in current thread (if async code), -> if not found -> Look for it in ExecutionContext

the second Issue here is that new StackTrace() doesn't contain a frames with sync code, from which async code is called.

API Proposal

MethodInfo mi = CurrentThread.GetCallerMethod();

API Usage

MethodInfo mi = CurrentThread.GetCallerMethod();

Alternative Designs

No response

Risks

No response

Author: feeleen
Assignees: -
Labels:

api-suggestion, area-System.Reflection, untriaged

Milestone: -

@DaZombieKiller
Copy link
Contributor

This seems like a subset of dotnet/csharplang#4984

@buyaa-n
Copy link
Contributor

buyaa-n commented Oct 25, 2021

I am not sure it is doable in reflection, probably better to be moved to System.Diagnostics? cc @steveharter

@ghost
Copy link

ghost commented Nov 3, 2021

Tagging subscribers to this area: @tommcdon
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

Current speс assumes to use CallerMemberName attribute to know caller name.
But this require to write a lot of excessive/garbage code, and do not cover all situations. And this sometimes results in ".ctor" values which is useless.

If we want to know caller name, we don't want see constructors (".ctor") as a result (because it's a useless info), we want to know caller Method name (omitting implicit or explicit constructor calls chain!)

In scenario below the caller member is empty because of implicit constructor calls.

using System;
using System.Runtime.CompilerServices;

class Program
{
    static void Main()
    {
        B b = new();
    }
}

public class B : A {}

public class A
{
    public A([CallerMemberName] string? value = null)
    {
        Console.WriteLine(value);
    }
}

If I have dll with public class A which relies on CallerMemberName attribute in it's constructor - it won't work because I can't control how descendant classes are defined.

I tried this workaround to discover caller member:

public A([CallerMemberName] string? value = null)
{
      new StackTrace().GetFrames().ToList()
                .ForEach(x => Console.WriteLine(x.GetMethod()?.Name))
}

resut is:

.ctor
.ctor
Main

I don't need ".ctor". I look for Main. Ok.

But this works only for sync code. When it comes to async calls - the StackTrace I get doesn't contain a sync caller member ("Main") at all, because the call chain is broken here:

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Foo().GetAwaiter().GetResult();
    }

    static async Task Foo()
    {
        await Task.Delay(100);
        B b = new();
    }
}

public class B : A {}

public class A
{
    public A([CallerMemberName] string? value = null)
    {
        // in this stackTrace we know nothing about Main
        new StackTrace().GetFrames().ToList()
            .ForEach(x => Console.WriteLine(x.GetMethod()?.Name));

        // here we will see Main:
        throw new Exception();
    }
}

console output is:

.ctor
.ctor
MoveNext
ExecutionContextCallback
RunInternal
MoveNext
MoveNext
<OutputWaitEtwEvents>b__12_0
Invoke
RunOrScheduleAction
RunContinuations
FinishContinuations
TrySetResult
CompleteTimedOut
<.ctor>b__1_0
CallCallback
Fire
FireNextTimers
AppDomainTimerCallback

There is no "Main" here. Chain is broken.

but if we look at thrown Exception output - we can see "Program.Main()". chain isn't broken.

   at A..ctor(String value) in H:\My programming\_VS2022\ConsoleApp1\ConsoleApp1\Program.cs:line 35
   at B..ctor()
   at Program.<Foo>d__1.MoveNext() in H:\My programming\_VS2022\ConsoleApp1\ConsoleApp1\Program.cs:line 18
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Program.Main() in C:\Temp\ConsoleApp1\ConsoleApp1\Program.cs:line 12

So,

It would be great if caller name or caller MethodInfo (omit constructors! they are useless) to be stored somewhere in ExecutionContext when it's captured, and to be able to get this info somehow from sync or async code. To get it I want to call CurrentThread.CallerMemberName or something like CurrentThread.GetCallerMethod(). It'll look for callers in current thread (if async code), -> if not found -> Look for it in ExecutionContext

the second Issue here is that new StackTrace() doesn't contain a frames with sync code, from which async code is called.

API Proposal

MethodInfo mi = CurrentThread.GetCallerMethod();
//or
MethodInfo mi = MethodBase.GetCallerMethod();

API Usage

MethodInfo mi = CurrentThread.GetCallerMethod();
//or
MethodInfo mi = MethodBase.GetCallerMethod();

Alternative Designs

No response

Risks

No response

Author: feeleen
Assignees: -
Labels:

api-suggestion, area-System.Diagnostics, area-System.Reflection

Milestone: Future

@joperezr
Copy link
Member

joperezr commented Nov 3, 2021

Just to add here, I agree with @buyaa-n that this API wouldn't belong on System.Reflection MethodInfo as MethodInfo is just an object that represents the pointer to a method and has info that are not related to current execution. Perhaps this kind of API would be better placed in a type like StackTrace?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Diagnostics
Projects
None yet
Development

No branches or pull requests

5 participants