Как отправить метод на основе типа времени выполнения параметра в C# ‹ 4?

У меня есть объект o, который во время выполнения гарантированно относится к одному из трех типов A, B или C, каждый из которых реализует общий интерфейс I. Я могу управлять I, но не A, B или C. (Таким образом, я мог бы использовать пустой интерфейс маркера или каким-то образом воспользоваться сходством типов с помощью интерфейса, но я не могу добавлять новые методы или изменять существующие в типах.)

У меня также есть ряд методов MethodA, MethodB и MethodC. Выполняется поиск типа среды выполнения o, который затем используется в качестве параметра для этих методов.

public void MethodA(A a) { ... }
public void MethodB(B b) { ... }
public void MethodC(C c) { ... }

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

public void Method(A a) { ... } // these are all overloads of each other
public void Method(B b) { ... }
public void Method(C c) { ... }

Теперь я позволяю C# выполнять отправку вместо того, чтобы делать это вручную. Можно ли это сделать? Наивный прямолинейный подход, конечно, не работает:

Не удается разрешить метод «Метод (объект)». Кандидаты:

  • Пустой метод (А)
  • Пустой метод (B)
  • Пустой метод (С)

person Evan Barkley    schedule 12.05.2010    source источник
comment
Если бы были типы D, E и F, нужна ли им всем реализация метода?   -  person Jason Punyon    schedule 12.05.2010
comment
@Jason: По-видимому, да, но в моем конкретном случае другие такие типы вряд ли когда-либо будут существовать.   -  person Evan Barkley    schedule 12.05.2010
comment
Кстати: +1 за хороший и хорошо заданный вопрос. Очевидно, что вы знаете свои варианты и знакомы с передовой практикой проектирования.   -  person Kobi    schedule 12.05.2010


Ответы (5)


Как насчет чего-то подобного?

private Dictionary<Type, Action<I>> _mapping = new Dictionary<Type, Action<I>>
{ 
  { typeof(A), i => MethodA(i as A)},
  { typeof(B), i => MethodB(i as B)},
  { typeof(C), i => MethodC(i as C)},
};

private void ExecuteBasedOnType(object value)
{
    if(_mapping.ContainsKey(value.GetType()))
       _mapping(value.GetType())(value as I);
}
person Brian Genisio    schedule 12.05.2010
comment
Ха, это на самом деле то, что у меня сейчас есть! Очевидно, что это не так хорошо, как позволить C# каким-то образом выполнять диспетчеризацию. - person Evan Barkley; 12.05.2010
comment
Если у вас есть контроль над I, A, B и C, вы можете добавить к I что-то, называемое ExecuteMethod(), и просто вызвать его для I. Если нет, вам нужно каким-то образом сопоставить его. - person Brian Genisio; 12.05.2010
comment
Это ваш лучший вариант, если вы не можете применить предложение Коби. Шаблон двойной отправки/посетителя не может быть выполнен, если вы также не контролируете классы. - person Rob Fonseca-Ensor; 12.05.2010
comment
Я думаю, что это лучший путь, поэтому я принимаю этот ответ. - person Evan Barkley; 12.05.2010
comment
Если вы примете это предложение и сделаете _mapping статическим и измените ExecuteBasedOnType на метод расширения I, вы получите хороший синтаксис, как в предложении Коби, без рефакторинга A, B и C. - person Cornelius; 12.05.2010
comment
@Корнелиус: Это умно! Я сделаю это. - person Evan Barkley; 12.05.2010
comment
Это в основном то, что делает динамическая функция в C # 4. (За исключением, конечно, того, что он строит лямбда-выражения и таблицу поиска по мере необходимости, вместо того, чтобы знать их заранее.) - person Eric Lippert; 12.05.2010
comment
Кажется, для этого есть специальный класс: KeyedByTypeCollection: msdn.microsoft.com/en -us/library/ms404549.aspx . Документация сбивает с толку, но может быть именно для таких случаев (хотя не уверен, я только что нашел ее, и она напомнила мне об этом ответе). - person Kobi; 01.06.2010

Если вы можете реорганизовать это, переместите метод в интерфейс, и каждый класс будет иметь свою реализацию:

I i = o as I;
i.Method();
person Kobi    schedule 12.05.2010
comment
Я отредактировал, чтобы уточнить, что я управляю I, но не A, B или C. Но это было бы здорово, если бы я мог! - person Evan Barkley; 12.05.2010
comment
@Evan, используйте частичные классы (см. мой ответ) - person tster; 12.05.2010
comment
@ Эван, как ты получаешь o? Если вы контролируете функцию, которая ее возвращает, вы можете наследовать от A B и C, и новые классы реализуют новый интерфейс. Я полагаю, вы тоже не контролируете это, поэтому вы не получите новые типы... - person Kobi; 12.05.2010
comment
@tster Если вы делаете класс частичным, все его частичные объявления должны быть сделаны в одной сборке, и во всех объявлениях класса должны быть отмечены как частичные, поэтому вам все равно нужен контроль над A, B и C, и если у вас есть контроль над ним, вы так же хорошо рефакторинг кода, как в предложении Коби. - person Cornelius; 12.05.2010
comment
@ Корнелиус, сам факт, что A, B и C наследуются от I, который он контролирует, заставляет меня думать, что он хоть немного контролирует сами классы. Думаю, именно поэтому этот вопрос такой странный. - person tster; 12.05.2010
comment
Это действительно правильный ответ, когда я могу контролировать типы реализации. Я хотел бы отметить два ответа принятыми. - person Evan Barkley; 12.05.2010
comment
@tster: Да, это очень странная ситуация. A, B и C являются сгенерированными частичными классами, которые я не могу коснуться (по распоряжению), но они являются частичными. Применение интерфейса, который просто использует преимущества сходства, не считается прикосновением к нему, поэтому мне это сойдет с рук. - person Evan Barkley; 12.05.2010
comment
@Evan, это та ситуация, для которой существуют частичные классы. Добавьте интерфейс, как я рекомендую в своем ответе, а затем реализуйте этот интерфейс для каждого класса в отдельном файле из созданного! - person tster; 13.05.2010
comment
@​​​​​​​​​​​​​tster — частичные классы здесь не помогут, их нельзя использовать, если у вас нет исходников AB и C — это просто способ организации кода в нескольких файлов и не действует после компиляции. Попробуйте. В документации говорится, что все части должны быть доступны во время компиляции для формирования окончательного типа... Все определения частичного типа, предназначенные для частей одного и того же типа, должны быть определены в одной сборке - msdn.microsoft.com/en-us/library/wa80x488%28VS.80%29. aspx - person Kobi; 13.05.2010

Если 3 класса A, B и C реализуют интерфейс I, вам ничего не нужно делать. Это среда выполнения, которая выбирает правильный метод для вас:

A a = new A();
class.Method(a); // will calll Method(A a)

B b = new B();
class.Method(b); // will call Method(B b)
person ema    schedule 12.05.2010
comment
Это неправильно отвечает на вопрос (или, по крайней мере, неправильно понимает, что я говорил). У меня нет A или B; У меня есть object (см. первое предложение). - person Evan Barkley; 12.05.2010

Я не уверен на 100%, сработает ли это в вашем сценарии, но похоже, что вы сможете использовать частичные классы:

public interface IMethodCallable
{
    public void Method();
}

public partial class A : IMethodCallable
{
    public void Method()
    {
        ....
    }
}

затем для использования:

object o = getObject(); // we know this is A, B or C
((IMethodCallable)o).Method();
person tster    schedule 12.05.2010

Предположительно O объявляется как тип/интерфейс I, а затем реализуется либо как a b, либо как c. Способ сделать это состоит в том, чтобы иметь один общедоступный метод, который принимает параметр типа I, а затем выполняет вашу логику в этом методе, например. вызвать закрытый метод A B или C.

Читая ваш отредактированный пост, o имеет тип объекта, помните, что нужно быть ясным, когда вы говорите об объекте, потому что, будучи типом С#, это общий термин oop для экземпляра класса.

Почему вы объявляете его как объект, а не i, может быть, позже это будет что-то другое?

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

if (o.GetType() == typeof(a))
{
   oa = (a)o;
   Method(oa);
}
else if(o.GetType() == typeof(b))
{
    ...
}

И т.п.

В качестве альтернативы, если вы заставили метод принимать параметр типа i, вы могли бы сделать что-то вроде:

i oi = (i)o;
Method(oi);

Но вам все равно нужно будет сделать что-то вроде первого примера в вашем общедоступном методе.

person Ben Robinson    schedule 12.05.2010