Определить тип вызывающего объекта в C # - PullRequest
14 голосов
/ 18 марта 2009

Независимо от того, является ли это хорошей идеей, возможно ли реализовать интерфейс, в котором исполняющая функция знает тип вызывающего объекта?

class A
{
   private C;

   public int doC(int input)
   {
      return C.DoSomething(input);
   }
}

class B
{
   private C;

   public int doC(int input)
   {
      return C.DoSomething(input);
   }
}

class C
{
   public int DoSomething(int input)
   {
      if(GetType(CallingObject) == A)
      {
         return input + 1;
      }
      else if(GetType(CallingObject) == B)
      {
         return input + 2;
      } 
      else
      {
         return input + 3;
      }
   }
}

Мне кажется, что это плохая практика кодирования (потому что параметры не меняются, а вывод будет), но кроме этого возможно ли это?

Я нахожусь в ситуации, когда я хочу, чтобы несколько определенных типов могли вызывать определенную функцию, но я не могу исключить доступ к этой функции. Я думал о наличии параметра "type"

DoSomething(int input, Type callingobject)

Но нет гарантии, что вызывающий объект будет использовать GetType (this), в отличие от GetType (B), чтобы подделать B независимо от их собственного типа.

Будет ли это так же просто (относительно просто), как изучение стека вызовов?


РЕДАКТИРОВАТЬ

Пожалуйста, проголосуйте за ответ ДжаредПара (если хотите), а также за Джона Феминеллу. Я не мог пометить оба ответа как принятые, и я принял ответ Джона Феминеллы, потому что это именно то, о чем я просил, а не ответ Джареда, который привел к решению, которое я ранее не рассматривал.

Ответы [ 11 ]

17 голосов
/ 18 марта 2009

Во-первых, да, это ужасная идея, которая нарушает все виды основательных принципов проектирования. Вы должны определенно рассмотреть альтернативный подход, если он открыт, например, просто использовать полиморфизм - кажется, что он может быть подвергнут рефакторингу для довольно ясного случая одиночной отправки.

Во-вторых, да, это возможно. Используйте System.Diagnostics.StackTrace для обхода стека; затем получите соответствующий StackFrame на один уровень выше. Затем определите, какой метод был вызывающим, используя GetMethod() для этого StackFrame. Обратите внимание, что построение трассировки стека является потенциально дорогостоящей операцией, и вызывающие ваши методы могут скрыть, откуда на самом деле все происходит.

<Ч />

Редактировать: Этот комментарий из ОП проясняет, что это может быть универсальный или полиморфный метод. @ devinb , вы можете рассмотреть вопрос о создании нового вопроса, в котором более подробно рассказывается о том, что вы пытаетесь сделать, и мы можем увидеть, хорошо ли он подходит для хорошего решения.

Короткая версия - я бы в итоге иметь 30 или 40 идентичных функций, которые были просто на одну или две строки. - devinb (12 секунд назад)

13 голосов
/ 18 марта 2009

В качестве альтернативного подхода рассматривали ли вы когда-либо предложение другого класса в зависимости от типа объекта, который запрашивает этот класс. Скажите следующее

public interface IC {
  int DoSomething();
}

public static CFactory { 
  public IC GetC(Type requestingType) { 
    if ( requestingType == typeof(BadType1) ) { 
      return new CForBadType1();
    } else if ( requestingType == typeof(OtherType) { 
      return new CForOtherType();
    }  
    ...
  }
}

Это было бы намного чище, чем каждый метод изменял бы свое поведение в зависимости от вызывающего объекта. Это четко разделило бы проблемы для различных реализаций IC. Кроме того, все они могут вернуться к реальной реализации Си.

РЕДАКТИРОВАТЬ Изучение стека вызовов

Как указали несколько других людей, вы можете изучить стек вызовов, чтобы определить, какой объект немедленно вызывает функцию. Однако это не надежный способ определить, вызывает ли вас один из объектов, который вы хотите использовать в особом случае. Например, я могу сделать следующее, чтобы позвонить вам из SomeBadObject, но вам будет очень трудно определить, что я это сделал.

public class SomeBadObject {
  public void CallCIndirectly(C obj) { 
    var ret = Helper.CallDoSomething(c);
  }
}

public static class Helper {
  public int CallDoSomething(C obj) {
    return obj.DoSomething();
  }
}

Конечно, вы можете пройти дальше по стеку вызовов. Но это еще более хрупко, потому что SomeBadObject может быть абсолютно законным путем в стеке, когда другой объект вызывает DoSomething.

4 голосов
/ 17 ноября 2012

Начиная с Visual Studio 2012 (.NET Framework 4.5), вы можете автоматически передавать информацию о вызывающем абоненте в метод с помощью атрибутов вызывающего (VB и C #).

public void TheCaller()
{
    SomeMethod();
}

public void SomeMethod([CallerMemberName] string memberName = "")
{
    Console.WriteLine(memberName); // ==> "TheCaller"
}

Атрибуты вызывающего абонента находятся в пространстве имен System.Runtime.CompilerServices.


Это идеально подходит для реализации INotifyPropertyChanged:

private void OnPropertyChanged([CallerMemberName]string caller = null) {
     var handler = PropertyChanged;
     if (handler != null) {
        handler(this, new PropertyChangedEventArgs(caller));
     }
}

Пример:

private string _name;
public string Name
{
    get { return _name; }
    set
    {
        if (value != _name) {
            _name = value;
            OnPropertyChanged(); // Call without the optional parameter!
        }
    }
}
2 голосов
/ 18 марта 2009

Не надежно из-за возможности встраивания во время выполнения.

2 голосов
/ 18 марта 2009

Самый простой ответ - передать объект отправителя, как любое событие с типичной методикой отправителя, eventargs.

Ваш код вызова будет выглядеть так:

return c.DoSomething(input, this);

Ваш метод DoSomething просто проверит тип с помощью оператора IS:

public static int DoSomething(int input, object source)
{
    if(source is A)
        return input + 1;
    else if(source is B)
        return input + 2;
    else
        throw new ApplicationException();

}

Это похоже на что-то с немного большим ООП. Вы можете считать C абстрактным классом с методом, а наличие A, B наследуется от C и просто вызывать метод. Это позволит вам проверить тип базового объекта, который явно не подделан.

Из любопытства, что вы пытаетесь с помощью этой конструкции?

0 голосов
/ 08 июля 2011

У меня могут быть проблемы с мыслью, что я бы справился с этим по-другому, но ....

Я предполагаю это:

класс А звонит в класс Е для информации
класс C обращается к классу E за информацией
оба одинаковыми в E

Вы знаете и создали все три класса и можете скрыть содержание класса E для общественности. Если бы это было не так, ваш контроль всегда был бы потерян.

Логически: если вы можете скрыть содержимое класса E, вы, скорее всего, сделаете это, распространяя его через DLL.

Если вы все равно это сделаете, почему бы не скрыть содержимое классов A и C (тот же метод), но разрешить использование A и C в качестве базы для производных классов B и D.

В классах A и C у вас будет метод для вызова метода в классе E и доставки результата. В этом методе (используемом производным классом, но контент скрыт для пользователей) вы можете иметь длинные строки в виде «ключей», передаваемых методу в классе E. Эти строки не могут быть угаданы любым пользователем и будут известны только в базовые классы A, C и E.

Наличие базовых классов, инкапсулирующих знания о том, как правильно вызывать метод в E, было бы идеальной реализацией ООП, я думаю

Опять же ... Я мог бы пропустить здесь сложности.

0 голосов
/ 18 марта 2009

структурируйте его как обработчик событий, я уверен, что FX-полицейский даже предложил бы вам сделать это

static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            throw new NotImplementedException();
        }
0 голосов
/ 18 марта 2009

Вы можете проверить стек вызовов, но это и дорого, и хрупко. Когда ваш код jit'ed, компилятор может встроить ваши методы, чтобы он мог работать в режиме отладки, и вы могли получить другой стек при компиляции в режиме выпуска.

0 голосов
/ 18 марта 2009

Вы можете использовать класс System.Diagnostics.StackTrace для создания трассировки стека. Тогда вы можете найти StackFrame, который связан с вызывающим абонентом. У StackFrame есть свойство Method, которое можно использовать для определения типа вызывающего абонента.

Тем не менее, вышеупомянутый метод не является тем, что должно использоваться в критичном для производительности коде.

0 голосов
/ 18 марта 2009

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

Как насчет интерфейса, который A,B будет реализовывать?

interface IFoo { 
     int Value { get; } 
}

И тогда ваш DoSomething метод будет выглядеть так:

   public int DoSomething(int input, IFoo foo)
   {
        return input + foo.Value;
   }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...