Определить тип объекта вызова в С#

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

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 (это), в отличие от GetType (B), чтобы обманывать B независимо от их собственного типа.

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


EDIT

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

Ответы

Ответ 1

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

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


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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Ответ 4

Самый простой ответ - передать объект-отправитель, как любое событие с типичной отправителем, методологию 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 и просто вызвать метод. Это позволит вам проверить тип базового объекта, который явно не подделан.

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

interface IFoo { 
     int Value { get; } 
}

И тогда ваш метод DoSomething будет выглядеть следующим образом:

   public int DoSomething(int input, IFoo foo)
   {
        return input + foo.Value;
   }

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            throw new NotImplementedException();
        }

Ответ 11

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

Я предполагаю следующее:

класс A вызывает класс E для информации
класс C вызывает класс E для информации
как через тот же метод в E

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

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

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

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

Если базовые классы инкапсулируют знание того, как правильно называть метод в E, было бы идеальной реализацией OOP. Думаю,

Тогда снова... Я мог бы игнорировать сложности здесь.