39

I got this class

public class fooBase
{
    public List<MethodsWithCustAttribute> MethodsList;
    public bool fooMethod([CallerMemberName]string membername =""))
    {
        //This returns a value depending of type and method 
    } 
    public void GetMethods()
    {
        // Here populate MethodsList using reflection
    }
}

And This Attribue Class

// This attribute get from a database some things, then fooMethod check this attribute members 
public class CustomAttribute
{
    public string fullMethodPath;
    public bool someThing ; 
    public bool CustomAttribute([CallerMemberName]string membername ="")
    {
        fullMethodPath = **DerivedType** + membername 
        //  I need here to get the type of membername parent. 
        //  Here I want to get CustClass, not fooBase

    }
}

Then I have this

public class CustClass : fooBase
{
     [CustomAttribute()]
     public string method1()
     {
         if (fooMethod())
         {
             ....
         }
     }
}

I need the Type name of the CallerMember, there is something like [CallerMemberName] to get the Type of class owner of the Caller ?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Juan Pablo Gomez
  • 5,203
  • 11
  • 55
  • 101
  • The information that `CompilerServices` provides is too little in my opinion to get the type from the calling method. But what you could try is actually going to the file using `[CallerFilePath]`, then goto the line given by the `[CallerLineNumber]` and figure out from there the class name. Then use reflection on the calling assembly to get the `Type` from the name you got. Or.. Considering the horrible performance and security issues.. Provide the `Type` as an argument is the easiest choice. – NucS Jul 27 '13 at 03:21
  • "Provide the Type as an argument is the easiest choice" Just this is that I don't want, if i have a class with 20 methods need to provide the class name for each one, if need to refactor this would be a head pain. Tks I'm considering this way but really don't like it. – Juan Pablo Gomez Jul 27 '13 at 03:27
  • walking up the stack to determine the caller type is pretty expensive, why do you need this? Is there another way to get the same results you desire? – Jay Jul 27 '13 at 03:30
  • @Jay In my application there is some supervised things, I need to check wich one methods of a class are marked into a db as supervised. – Juan Pablo Gomez Jul 27 '13 at 03:32
  • So you only want certain types calling into these methods? If you have any control at all over the calling methods, you should start there, not branching logic after the call is made. – Jay Jul 27 '13 at 03:47
  • @Jay Yes. Thats why I opted for mark Classes and Methods with custom attributes. But if just use Method names could happen that Two or more classes has the same method name because of this I need the type name. – Juan Pablo Gomez Jul 27 '13 at 03:53

4 Answers4

35

It isn't foolproof, but the convention with .NET is to have one type per file and to name the file the same as the type. Our tooling also tends to enforces this convention i.e. Resharper & Visual Studio.

Therefore it should be reasonable to infer the type name from the file path.

public class MyClass
{
  public void MyMethod([CallerFilePath]string callerFilePath = null, [CallerMemberName]string callerMemberName = null)
  {
    var callerTypeName = Path.GetFileNameWithoutExtension(callerFilePath);
    Console.WriteLine(callerTypeName);
    Console.WriteLine(callerMemberName);
  }
}
Paul
  • 3,125
  • 2
  • 24
  • 21
  • 15
    I wish there was a [CallerMemberType]! Since the StackTrace approach is shaky due to inlining and the fact that it's not implemented for UWP, this is the best I've found so far. – thehelix Sep 12 '18 at 19:01
  • 1
    CallerMemberType not available now – Kiquenet May 13 '19 at 15:23
  • "but the convention with .NET is to have one type per file" - this is news to me. I know Java enforces this silly requirement, but Microsoft's own Best Practices examples are full of `.cs` files with multiple types defined in the same file. – Dai Jan 23 '21 at 17:30
  • 1
    @Dai isnt that just for the sake of brevity/code examples? I dont think thats what you want to do when it comes to an enterprise app for example – heug Oct 07 '21 at 17:21
  • @heug I develop enterprise apps for twenty years and often break the 1 class per file rule. mostly when small classes, interface implementers with little code or boilerplate code are related to each other and it makes the arrangement more readable or understandable if seen together. however I would not do that for business logic classes or complicate algorithms. it is not about enterprise or small app. – Daniel Leiszen Jun 14 '22 at 13:00
16

Caller member

Granted, getting the caller member name is not "natural" in the object model. That's why the C# engineers introduced CallerMemberName in the compiler.

The real enemy is duplication, and stack-based workarounds are inefficient.

[CallerMemberName] allows to get the information without duplication and without ill-effect.

Caller type

But getting the caller member type is natural and easy to get without duplication.

How to do it

Add a "caller" parameter to fooMethod, no special attribute needed.

    public bool fooMethod(object caller, [CallerMemberName]string membername = "")
    {
        Type callerType = caller.GetType();
        //This returns a value depending of type and method
        return true;
    }

And call it like this:

fooMethod(this);

This answer the question

You stated

// Here I want to get CustClass, not fooBase

and that's exactly what you'll get.


Other situations where it would not work, with solutions.

While this exactly answers your requirements, there are other, different, cases where it wouldn't work.

  • Case 1: When caller is a static methods (there is no "this").
  • Case 2: When one wants the type of the caller method itself, and not the type of the caller itself (which may be a subclass of the first).

In those cases, a [CallerMemberType] might make sense, but there are simpler solutions. Notice that the static caller case is simpler: there is no object so no discrepancy between it and the type of the calling method. No fooBase, only CustClass.

Case 1: When caller is a static methods (there is no "this")

If at least one caller is a static method, then don't do the GetType() inside the method but on call site, so don't pass "this" to the method but the type:

public bool fooMethodForStaticCaller(Type callerType, [CallerMemberName]string membername = "")

Static caller will do:

public class MyClassWithAStaticMethod  // can be CustClass, too
{
    public static string method1static()
    {
        fooMethodForStaticCaller(typeof(MyClassWithAStaticMethod));
    }
}

To keep compatibility with object callers, either keep the other fooMethod that takes the this pointer, or you can remove it and object callers will do:

fooMethod(this.GetType());

You can notice that the typeof(MyClassWithAStaticMethod) above repeats the class name and it's true. It would be nicer to not repeat the class name, but it's not such a big deal because this repeats only once, as a typed item (not a string) and inside the same class. It's not as serious a problem as the original problem that the [CallerMemberName] solves, which was a problem of repeating the caller name in all call sites.

Case 2: When one wants the type of the caller method, not the type of the caller

For example, in class fooBase you want to call anotherFooMethod from object context but want the type being passed to always be fooBase, not the actual type of the object (e.g. CustClass).

In this case there is a this pointer but you don't want to use it. So, just use actually the same solution:

public class fooBase
{
     [CustomAttribute()]
     public string method1()
     {
         if (anotherFooMethod(typeof(fooBase)))
         {
             ....
         }
     }
}

Just like in case 1, there is one repetition, not one per call site, unless you have an pre-existing problem of rampant code duplication, in which case the problem being addressed here is not the one you should worry about.

Conclusion

[CallerMemberType] might still make sense to avoid duplication at all, but:

  • anything added to the compiler is a complexity burden with maintenance cost
  • given the existing solutions I'm not surprised there are items with higher priority in the C# development team list.
Stéphane Gourichon
  • 6,493
  • 4
  • 37
  • 48
  • Tks for the answer but what version of c# supports it ? – Juan Pablo Gomez Nov 13 '17 at 15:11
  • @JuanPabloGomez if you need only type of caller not method name, then you don't need `[CallerMemberName]` and .NET 1.0 is enough. The implementation is just using `Object.GetType()` on a regular method parameter. That's the whole point of this answer: sometimes, simple things are simple and have been simple for ages. :-) – Stéphane Gourichon Nov 13 '17 at 15:26
  • Sorry I understand your point now. I was looking for how to get the CallerMemberTypeName. without the need to pass another parameter. But is a good suggestion – Juan Pablo Gomez Nov 14 '17 at 09:51
  • `fooMethod(this);` and for ***static method*** ? – Kiquenet May 13 '19 at 15:25
  • @Kiquenet it's been a while. Can you give a concrete example? It may help to offer you hints. – Stéphane Gourichon May 14 '19 at 04:03
  • In static method, you cannot use ***this***. I call `fooMethod()` – Kiquenet May 14 '19 at 07:03
  • @Kiquenet I know *this* is not available in static context, I wrote it in my answer. I wanted you to provide a concrete example of your need to make sure this is not a X-Y problem. Anyway, I adjust the answer to cover the static case, too. Please answer or vote. – Stéphane Gourichon May 15 '19 at 04:46
  • I think you can make it generic and use `T caller` instead. Feels cleaner imo. – jeromej Jun 03 '20 at 07:15
  • @jeromej Thanks for your suggestion. I suppose you're referring to first part of answer `fooMethod`. What practical benefit does the use of `T caller` bring? Are you sure it cannot on the contrary reduce value by being incompatible with some cases? – Stéphane Gourichon Jun 03 '20 at 11:25
  • Alas all of those are obtrusive, requiring change to the source code of ever caller. Imagine if this additional required parameter was added to a core .NET simple method - it would break every program in existance. – David V. Corbin Jun 02 '21 at 12:56
6

See Edit 2 for the better solution.

The information that CompilerServices provides is too little in my opinion to get the type from the calling method. What you could do is use StackTrace (see) to find the calling method (using GetMethod()) and get the type using Reflection from there.
Consider the following:

using System.Runtime.CompilerServices;

public class Foo {
    public void Main() {
        what();
    }

    public void what() {
        Bar.GetCallersType();
    }

    public static class Bar {

        [MethodImpl(MethodImplOptions.NoInlining)]  //This will prevent inlining by the complier.
        public static void GetCallersType() {
            StackTrace stackTrace = new StackTrace(1, false); //Captures 1 frame, false for not collecting information about the file
            var type = stackTrace.GetFrame(1).GetMethod().DeclaringType;
            //this will provide you typeof(Foo);
        }
    }
}

Notice - As @Jay said in the comments, it might be pretty expensive but it does the work well.

Edit:

I found couple of arcticles comparing the performance, and it is indeed horrbily expensive comparing to Reflection which is also considered not the best.
See: [1] [2]

Edit 2:

So after a look in depth on StackTrace, it is indeed not safe to use it and even expensive.
Since every method that will be called is going to be marked with a [CustomAttribute()], it is possible to collect all methods that contains it in a static list.

public class CustomAttribute : Attribute {
    public static List<MethodInfo> MethodsList = new List<MethodInfo>();
    static CustomAttribute() {
        var methods = Assembly.GetExecutingAssembly() //Use .GetCallingAssembly() if this method is in a library, or even both
                  .GetTypes()
                  .SelectMany(t => t.GetMethods())
                  .Where(m => m.GetCustomAttributes(typeof(CustomAttribute), false).Length > 0)
                  .ToList();
        MethodsList = methods;
    }

    public string fullMethodPath;
    public bool someThing;

    public  CustomAttribute([CallerMemberName] string membername = "") {
        var method = MethodsList.FirstOrDefault(m=>m.Name == membername);
        if (method == null || method.DeclaringType == null) return; //Not suppose to happen, but safety comes first
        fullMethodPath = method.DeclaringType.Name + membername; //Work it around any way you want it
        //  I need here to get the type of membername parent. 
        //  Here I want to get CustClass, not fooBase
    }
}

Play around with this approach to fit your precise need.

Community
  • 1
  • 1
NucS
  • 619
  • 8
  • 21
  • 3
    You need to really careful when walking the stack. Methods could be inlined when optimizations are on resulting the in the wrong type. – Mike Zboray Jul 27 '13 at 03:37
  • `MethodImplOptions.NoInlining` does not prevent inlining of the caller which will also affect your stack. This sample is too simple, but `what` could be inlined into `Main`. Here you get lucky and they are the same type, but in general they are not. – Mike Zboray Jul 27 '13 at 03:58
  • @mikez Do you have another approach ? I'm really stalled at this point. Tks for your help. – Juan Pablo Gomez Jul 27 '13 at 04:00
  • @mikez I really doubt it. Even if a method is inlined into another, it will still preserve its content of the original `Type` when it is being accessed by `Reflection`, nevertheless it is important to make sure `GetCallersType()` method won't be inlined. – NucS Jul 27 '13 at 04:03
  • @JuanPabloGomez No I do not. I'm just pointing out the limitations of this approach. – Mike Zboray Jul 27 '13 at 04:18
  • @NucS I have seen it happen [here](http://stackoverflow.com/a/14242352/517852) and in other projects I've worked on. – Mike Zboray Jul 27 '13 at 04:20
  • @NucS This method is located at the CustomAttribute. and it return my expected result at the frame 8, may I expect allways this result was the same ? – Juan Pablo Gomez Jul 27 '13 at 04:26
  • @JuanPabloGomez As @mikez said; if it is really the case and methods that are inlined can't be considered as a `Frame` then I'm afraid that expecting to get a method's type 8 frames behind is like trying to shoot a fly. and I'm out of ideas currently. – NucS Jul 27 '13 at 04:38
  • @JuanPabloGomez I suggest you to take a look at Edit 2. I've used this approach before and it is not deadly expensive. – NucS Jul 27 '13 at 04:59
  • Just pass whatever info the method needs as an argument. Using reflection is no substitute for that at all, wasn't made to do that. – Hans Passant Jul 29 '13 at 00:59
  • @NucS i really think you should remake your answer, that you make the real answer "edit 2" and then leave a lower section detailing the stack information with a very explicit warning saying "never use this, the results are indeterminate due to optimization". Or feel free to reply to me here and i'll do it for you to save your time. – Chris Marisic Jun 06 '17 at 14:55
1

Why not just use public void MyMethod<T>(params) { string myName = typeof(T).Name }

then call it Logger.MyMethod<Form1>(...);

You avoid the performance hit of reflection, if you just need basic info.

Nick Turner
  • 927
  • 1
  • 11
  • 21