Search code examples
c#callermembername

CallerMemberName is empty if used from base class constructor and not initialized


I have two classes like this:

public class test1: BaseClass
{
    public test1() : base()
    {
    }
...

public class BaseClass
{
    public BaseClass(
        [CallerMemberName]string membername ="",
        [CallerFilePath] string path = "")
    {
        var sf = new System.Diagnostics.StackTrace(1).GetFrame(0);
    }

If I specify test1 ctor with call to base - I get membername and path initialized properly, but if not - compiler generates default constructor call, and membername and path are both empty.

Is this a bug or a feature ?

(Visual Studio 2019 16.11.8, net core 3.1 or net 5.0).


Solution

  • I've googled "c# compiler github" and ended up on https://github.com/dotnet/roslyn

    After searching by CallerMemberName - I've managed to find answer for this question:

    https://github.com/dotnet/roslyn/issues/53757

    It mentions that this is done by design.

    But quickly scanning through the tickets lead me of thinking - "can I use attributes for same purpose ?"

    Since what I was doing was unit testing - I've recoded my own attribute for that purpose: TestAttribute => FactAttribute and resolved from NUnit method info that attribute, and got back file path and method name.

    public class FactAttribute : TestAttribute
    {
        public string FunctionName { get; }
        public string FilePath { get;  }
    
        public FactAttribute( [CallerMemberName] string functionName = "", [CallerFilePath] string filePath = "")
        {
            FunctionName = functionName;
            FilePath = filePath;
        }
    }
    
    [TestFixture]
    public class BaseClass
    {
        /// <summary>
        /// Accesses private class type via reflection.
        /// </summary>
        /// <param name="_o">input object</param>
        /// <param name="propertyPath">List of properties in one string, comma separated.</param>
        /// <returns>output object</returns>
        object getPrivate(object _o, string propertyPath)
        {
            object o = _o;
            var flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
            
            foreach (var name in propertyPath.Split('.'))
            {
                System.Type type = o.GetType();
    
                if (char.IsUpper(name[0]))
                    o = type.GetProperty(name, flags).GetValue(o);
                else
                    o = type.GetField(name, flags).GetValue(o);
            }
    
            return o;
        }
    
        [SetUp]
        public void EachSpecSetup()
        {
            var mi = (MemberInfo)getPrivate(TestContext.CurrentContext.Test, "_test.Method.MethodInfo");
            FactAttribute attr = mi.GetCustomAttribute<FactAttribute>();
            string path = attr.FilePath;
            string funcName = attr.FunctionName;
        }
    

    This allows to determine from which file and from which method call was directed from.