Эффективная проверка типов во время выполнения - PullRequest
9 голосов
/ 05 июня 2011

У меня есть иерархия классов, как следует (на самом деле у меня более 3 производных типов):

class A {};
class B : A  {};
class C : B  {};
class D : A  {};

Экземпляры этих классов хранятся в List<A> коллекциях. Иногда коллекции довольно большие (тысячи или даже десятки тысяч предметов).

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

List<A> collection = ...
foreach (A obj in collection)
{
    if (obj is C)
        something(obj as C);
    else if (obj is B)
        somethingElse(obj as B);
    ....
}

Как видите, код выполняет множество проверок типа объекта и приведений. Для коллекций со многими элементами производительность кода не так уж велика.

Что бы вы посоветовали ускорить проверки типов во время выполнения в моем случае?

Ответы [ 6 ]

11 голосов
/ 05 июня 2011

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

Тогда это становится:

foreach (A obj in collection)
{
    obj.DoSomething();
}

1012 * т.е. *

class A {
    public virtual void DoSomething() {...}
}
class B : A   {
    public override void DoSomething() {...}
}

и т.д.

4 голосов
/ 05 июня 2011

Сделайте функциональность, реализованную в функции «что-то», поведением / методом класса A, а затем переопределите его в дочерних классах в зависимости от обстоятельств (убедитесь, что метод определен как виртуальный).

В этом случае вы можете сразу позвонить:

List<A> collection = ...
foreach (A obj in collection)
{
  obj.something()
}
2 голосов
/ 05 июня 2011

Обычно чистое решение - это виртуальный метод. Но иногда это невозможно. В этом случае вы можете использовать Dictionary<Type,Action>.

Dictionary<Type,Action> actions=new Dict...;

Action action;
if(!actions.TryGetValue(obj.GetType(), out action))
{
  action=GetActionForType(obj.GetType());
  actions.Add(obj.GetType(), action);
}
action();

Обратите внимание, что это работает только для точных типов, а не для производных типов. Поэтому вам может понадобиться добавить логику, основанную на Type.IsAssignableFrom внутри GetActionForType.

И, очевидно, эта реализация не безопасна для потоков.

2 голосов
/ 05 июня 2011

Мне кажется, что вы должны просто использовать виртуальный метод.Это приведет к эффективному коду, который будет более читабельным.

2 голосов
/ 05 июня 2011

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

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

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

2 голосов
/ 05 июня 2011

Используйте только as и вместо этого проверьте нулевое значение:

C c = obj as C;
if (c != null)
    something(c);

Он будет выполнять приведение только один раз.И as, и is на самом деле выполняют кастинг, поэтому на самом деле нет необходимости использовать их вместе.

При этом, кастинг относительно дешев.Любые потери производительности для приведения должны быть уменьшены реализацией something(c) или something(b), поэтому не забывайте об этом, если только количество типов, к которым вы пытаетесь привести, не является действительно значительным.

Если вы находитесь вуправление классами A, B, C, посмотрите, сможете ли вы переработать модель, чтобы вам вообще не приходилось выполнять приведение типов - используйте виртуальные методы, как предлагают другие.

...