如何获取呼叫者成员的类型名称

本文关键字:成员 类型 呼叫者 何获取 获取 | 更新日期: 2023-09-27 18:25:19

我得到了这个类

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

和这个属性类

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

然后我有这个

public class CustClass : fooBase
{
     [CustomAttribute()]
     public string method1()
     {
         if (fooMethod())
         {
             ....
         }
     }
}
我需要调用者

成员的类型名称,有类似 [调用者成员名称] 的东西来获取调用者的类所有者的类型?

如何获取呼叫者成员的类型名称

它不是万无一失的,但 .NET 的约定是每个文件有一个类型,并将文件命名为与类型相同的名称。我们的工具也倾向于强制执行这个约定,即Resharper和Visual Studio。

因此,从文件路径推断类型名称应该是合理的。

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

调用方成员

当然,获取调用方成员名称在对象模型中并不"自然"。这就是 C# 工程师在编译器中引入 CallerMemberName 的原因。

真正的敌人是重复,而基于堆栈的解决方法效率低下。

[CallerMemberName]允许在没有重复和不良影响的情况下获取信息。

呼叫者类型

但是获取调用方成员类型很自然的,并且很容易获得,而不会重复。

怎么做

fooMethod中添加"caller"参数,不需要特殊属性。

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

并像这样称呼它:

fooMethod(this);

这回答了问题

你说

在这里我想得到CustClass,而不是fooBase

而这正是你会得到的。


其他不起作用的情况,并提供解决方案。

虽然这完全可以满足您的要求,但在其他不同的情况下,它不起作用。

  • 情况 1:当调用者是静态方法时(没有"this"(。
  • 情况 2:当需要调用者方法本身的类型,而不是调用者本身的类型(可能是第一个的子类(时。

在这些情况下,[CallerMemberType]可能是有意义的,但有更简单的解决方案。请注意,静态调用者的情况更简单:没有对象,因此它与调用方法的类型之间没有差异。没有fooBase,只有CustClass

情况 1:当调用方是静态方法时(没有"this"(

如果至少有一个调用方是静态方法,则不要在方法内部执行GetType(),而是在调用站点上执行,因此不要将"this"传递给方法,而是将类型传递给:

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

静态调用方将执行以下操作:

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

为了保持与对象调用方的兼容性,请保留采用this指针的其他fooMethod,或者您可以删除它,对象调用方将执行以下操作:

fooMethod(this.GetType());

您可以注意到上面的typeof(MyClassWithAStaticMethod)重复了类名,这是真的。 最好不要重复类名,但这没什么大不了的,因为这只重复一次,作为类型化项目(不是字符串(并且在同一类中。 这并不像[CallerMemberName]解决的原始问题那么严重,即在所有呼叫站点中重复呼叫者姓名的问题。

情况 2:当需要调用者方法的类型而不是调用者的类型

例如,在类fooBase中,您希望从对象上下文中调用anotherFooMethod,但希望传递的类型始终fooBase,而不是对象的实际类型(例如 CustClass(。

在这种情况下,有一个this指针,但您不想使用它。 因此,只需使用实际相同的解决方案:

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

就像在案例 1 中一样,有一个重复,而不是每个调用站点一个,除非您有一个预先存在的猖獗的代码重复问题,在这种情况下,这里要解决的问题不是你应该担心的问题。

结论

[CallerMemberType]避免重复可能仍然有意义,但是:

    添加到
  • 编译器的任何内容都是维护成本的复杂性负担
  • 鉴于现有的解决方案,我并不感到惊讶,C#开发团队列表中有优先级更高的项目。

有关更好的解决方案,请参阅编辑 2

在我看来,CompilerServices提供的信息太少,无法从调用方法中获取类型。你可以做的是使用 StackTrace (参见( 查找调用方法(使用 GetMethod() (,并从那里使用 Reflection 获取类型。
请考虑以下事项:

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

注意 - 正如@Jay在评论中所说,它可能非常昂贵,但它做得很好。

编辑:

我发现有几个北极比较性能,与Reflection相比,它确实非常昂贵,这也被认为是最好的。
参见: [1] [2]

编辑 2:

所以在深入了解StackTrace之后,使用它确实不安全,甚至昂贵。
由于每个将被调用的方法都将用[CustomAttribute()]标记,因此可以将包含它的所有方法收集到静态列表中。

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

尝试这种方法以满足您的确切需求。

为什么不直接使用public void MyMethod<T>(params) { string myName = typeof(T).Name }

那就叫它Logger.MyMethod<Form1>(...);

如果您只需要基本信息,则可以避免反射对性能的影响。