С# как получить имя типа вызывающего члена

Я получил этот класс

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())
         {
             ....
         }
     }
}

Мне нужно имя типа CallerMember, есть что-то вроде [CallerMemberName], чтобы получить Тип владельца класса Caller?

Ответ 1

Это не является надежным, но соглашение с .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);
  }
}

Ответ 2

Смотрите Edit 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
    }
}

Поиграйте с этим подходом, чтобы удовлетворить ваши конкретные потребности.

Ответ 3

Участник звонящего

Конечно, получение имени члена вызывающей стороны не является "естественным" в объектной модели. Вот почему инженеры С# представили CallerMemberName в компиляторе.

Настоящий враг - это дублирование, а обходные пути на основе стека неэффективны.

[CallerMemberName] позволяет получать информацию без дублирования и без негативных последствий.

Тип звонящего

Но получить тип члена вызывающей стороны естественно и легко получить без дублирования.

Как это сделать

Добавьте параметр "вызывающий" в fooMethod, специальный атрибут не требуется.

    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: когда вызывающим является статический метод (нет "этого").
  • Случай 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));
    }
}

Чтобы сохранить совместимость с вызывающими объектами, либо оставьте другой fooMethod который принимает указатель this, либо вы можете удалить его, и вызывающие объекты будут делать:

fooMethod(this.GetType());

Вы можете заметить, что typeof(MyClassWithAStaticMethod) выше повторяет имя класса и его значение true. Было бы лучше не повторять имя класса, но это не такая уж большая проблема, потому что это повторяется только один раз, как типизированный элемент (не строка) и внутри одного и того же класса. Это не такая серьезная проблема, как исходная проблема, [CallerMemberName] решает [CallerMemberName], которая была проблемой повторения имени вызывающего абонента на всех сайтах вызовов.

Случай 2: когда требуется тип метода вызывающего, а не тип вызывающего

Например, в классе fooBase вы хотите вызвать anotherFooMethod из контекста объекта, но хотите, чтобы передаваемый тип всегда был fooBase, а не фактическим типом объекта (например, CustClass).

В этом случае есть указатель this но вы не хотите его использовать. Итак, просто используйте то же решение:

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

Как и в случае 1, существует одно повторение, а не одно на каждый сайт вызова, если только у вас нет ранее существовавшей проблемы неуклонного дублирования кода, и в этом случае проблема, о которой идет речь, не та, о которой вам следует беспокоиться.

Заключение

[CallerMemberType] может иметь смысл вообще избегать дублирования, но:

  • все, что добавлено в компилятор, является сложным бременем со стоимостью обслуживания
  • Учитывая существующие решения, я не удивлен, что есть пункты с более высоким приоритетом в списке команды разработчиков С#.