Общий класс обертки

Учитывая следующую иерархию:

class A
{
}
class B : A
{
    public void Foo() { }
}
class C : A
{
    public void Foo() { } 
}

Это сторонняя библиотека, и я не могу ее изменить. Есть ли способ написать какую-то "общую шаблонную оболочку", которая перенаправляет метод Foo() на соответствующий объект, переданный как аргумент конструктора? В итоге я написал следующее, которое не использует никаких дженериков и кажется довольно уродливым:

class Wrapper
    {
        A a;
        public Wrapper(A a)
        {
            this.a = a;
        }

        public void Foo()
        {
            if (a is B) { (a as B).Foo(); }
            if (a is C) { (a as C).Foo(); }
        }

    }

Мне бы понравилось ограничение шаблона, например Wrapper<T> where T : B or C.

Ответ 1

Если A не имеет Foo, вам нужно либо использовать dynamic (см. ответ Jon Skeet), либо использовать небольшой трюк с лямбдами и перегрузка:

class Wrapper {
    private Action foo;
    public Wrapper(B b) {
        foo = () => b.Foo();
    }
    public Wrapper(C c) {
        foo = () => c.Foo();
    }
    public void Foo() {
        foo();
    }
}

Теперь вы можете сделать это:

var wb = new Wrapper(new B());
wb.Foo(); // Call B Foo()
var wc = new Wrapper(new C());
wc.Foo(); // Call C Foo()

Это смещает решение о том, какой метод вызывать с момента вызова Foo до момента создания Wrapper, что может сэкономить вам несколько циклов процессора.

Ответ 2

Нет, два метода Foo полностью не связаны с компилятором. Простейший способ сделать это, не зная о конкретных типах, с которыми нужно начать, - это использовать динамическое типирование:

public void Foo()
{
    dynamic d = a;
    // Let hope there a suitable method at execution time!
    d.Foo();
}

Дженерики вам не помогут, насколько я могу судить. Это не похоже на некоторый интерфейс (по крайней мере, тот, который вы показали), который вы можете ограничить T до.

Вы также можете передать Action:

Wrapper wrapper = new Wrapper(b, b.Foo);

что делает его немного менее удобным для вызывающего, но очень общего...

Ответ 3

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

class Wrapper
{
    public dynamic theAorBorC;

    public Wrapper(A a){theAorBorC=a;}
    public Wrapper(B b){theAorBorC=b;}
    public Wrapper(C c){theAorBorC=c;}

    // or even...
    // public Wrapper(object anything){theAorBorC=anything;}

    public void CallFoo()
    {
        theAorBorC.Foo();
    }
}

Изменить: во всех остальных случаях я лично использовал lambdas так же, как показал dasblinkenlight, - чтобы получить проверку времени компиляции. Он может быть легко автогенерирован, то есть с T4s или любым другим текстовым генератором.

Ответ 4

Вы можете создать параллельную иерархию, которая содержит метод Foo() на корневом уровне.
Используя методы factory, вы можете создать экземпляр Wrapper для любого типа. Для этого вам нужно знать точный тип вашего экземпляра A, когда вы вызываете метод factory

abstract class Wrapper {
    public abstract void Foo();

    //factory methods
    public Wrapper FromB(B instance) {
        return new WrapperB(instance);
    }
    public Wrapper FromC(C instance) {
        return new WrapperB(instance);
    }
}

class WrapperB {
    private B instance {get; set;}
    public WrapperB(B instance) {
        this.instance = instance;
    }

    public void Foo() {
        instance.Foo();
    }
}
class WrapperC {
    private C instance {get; set;}
    public WrapperC(C instance) {
        this.instance = instance;
    }

    public void Foo() {
        instance.Foo();
    }
}

Изменить: это в основном то же самое, что этот ответ