приведение в C # и вызов определенного метода на основе производного типа - PullRequest
5 голосов
/ 27 июля 2011

У меня есть пара классов, все они происходят от одного базового типа.

class basetype{}
class TypeA : basetype{}
class TypeB : basetype{}
...

Количество из них хранится в списке.

List<basetype> myObjects

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

HandleTypes(TypeA obj){}
HandleTypes(TypeB obj){}
HandleTypes(basetype obj)

В настоящее время мой HandleAllTypes выглядит так:

string name = obj.GetType().Name
switch(name)
{
  case "TypeA":
    return HandleTypes(obj as TypeA);
  case "TypeB":
    return HandleTypes(obj as TypeB);
  ....
}

теперь это дерьмо. Есть ли способ, подобный

HandleTypes(obj ?"as derived type"?)

Искал через MSDN и другие источники, ничего не нашел.

Ответы [ 8 ]

7 голосов
/ 27 июля 2011

Как насчет

HandleTypes( obj as dynamic );


Я использовал это пару раз, когда мне приходилось иметь дело со сторонними классами. Это также может быть очень полезно, когда есть много производных классов.

Вы можете легко проверить, реализована ли функция обработки:

try {
   HandleTypes( obj as dynamic )
}
catch( RuntimeBinderException ex ) {
   // not implemented
}
4 голосов
/ 27 июля 2011
class basetype{
  public virtual void Handle(){
     // do only for base type 
  }
} 
class TypeA : basetype{
  public override void Handle(){
     // do only for Atype
  }
}
class TypeB : basetype{
  public override void Handle(){
     // do only for Btype
  }
}

foreach(baseType obj in myObjects)
    obj.Handle() 
1 голос
/ 27 июля 2011

Вот другой способ получить то, что вы пытаетесь сделать.

Методы:

void HandleTypes(IEnumerable<Apple> apples)
void HandleTypes(IEnumerable<Banana> banana)
void HandleTypes(IEnumerable<Orange> oranges)
void HandleTypes(IEnumerable<Fruit> fruit)

Вызывается по:

List<Fruit> fruitbasket = GetBasket();

HandleTypes(fruitbasket.OfType<Apple>());
HandleTypes(fruitbasket.OfType<Orange>());
HandleTypes(fruitbasket.OfType<Banana>());
HandleTypes(fruitbasket.OfType<Fruit>());

Или вызывается по:

List<Fruit> fruitbasket = GetBasket();
ILookup<Type, Fruit> fruitLookup = fruitBasket.ToLookup(x => x.GetType());

foreach(IGrouping<Type, Fruit> fruitRollup in fruitLookup)
{
  switch(fruitRollup.Key.Name)
  {
    case "Apple" :
      return HandleTypes(fruitRollup.OfType<Apple>());
      break;
    case "Banana" :
      return HandleTypes(fruitRollup.OfType<Banana>());
      break;
    case "Orange" :
      return HandleTypes(fruitRollup.OfType<Orange>());
      break;
    case "Fruit" :
      return HandleTypes(fruitRollup.OfType<Fruit>());
      break;
    default :
      return HandleTypes(fruitRollup.OfType<Fruit>());
      break;
  }
}
1 голос
/ 27 июля 2011

Что вам нужно, это двойная отправка , которая недоступна непосредственно в C #.Решение, основанное на шаблоне посетителя , может использоваться для имитации двойной диспетчеризации, если basetype объявить абстрактный метод Accept, который делает вызов посетителю, где при перегрузке будет выбран правильный метод.

abstract class basetype
{
  //..
  public abstract void Accept(Visitor v);
  //..
}

class TypeA
{
  //..
  //..
  public override void Accept(Visitor v) { v.Visit(this); }
}

abstract class Visitor
{
  public abstract void Visit(TypeA a);
  public abstract void Visit(TypeB b);
}

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

1 голос
/ 27 июля 2011

Я думаю, что здесь вам нужны виртуальные методы.

По сути, вы объявляете виртуальный метод в своем базовом классе, который называется скажем, DoWork ().

Теперь вы можете переопределить этот виртуальный метод в TypeA. Вы также можете переопределить его в TypeB.

Если вы вызовете DoWork () для базового объекта, будет использован его метод. Если вы вызовете DoWork () для объекта типа A, будет использован его метод.

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

Подробнее: http://msdn.microsoft.com/en-us/library/aa645767(v=vs.71).aspx

1 голос
/ 27 июля 2011

Обычно вы, вероятно, реализуете свою функциональность 'HandleTypes' для basetype / TypeA / TypeB, и просто вызываете obj.HandleTypes(), и полиморфизм обрабатывает это. По какой причине ты не можешь этого сделать?

1 голос
/ 27 июля 2011

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

Для разделения типов я склонен делать что-то вроде этого:

TypeA aRef = obj as TypeA;

if (aRef != null)
    HandleTypes(aRef);

Однако идеальным способом было бы использовать наследование и поместить метод HandleType в базовый класс, сделать его virtual и override в производных типах, где это необходимо. Однако иногда это не вариант по какой-либо причине.

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

То, что вы просите, невозможно. Вызовы методов разрешаются во время компиляции и впоследствии не изменяются, но вы просите выбрать конкретный метод на основе значения времени выполнения. Это было бы возможно только с использованием делегатов, но даже с делегатами вы не сможете преобразовать аргумент в более производный тип, чем тот, который указан в объявлении делегата.

...