Можно ли применить к интерфейсу, который не наследуется?

Почему не возможно создать экземпляр:

sealed class Foo
{
    public void Go() { }
}

... к этому интерфейсу:

interface IBar
{
    void Go();
}

... хотя Foo имеет подпись IBar?

Как я могу превратить экземпляр Foo в IBar? Предположим, что я не контролирую Foo.

Ответ 1

Нет, С# не поддерживает утиную печать.

Подход OOP к этой проблеме состоит в том, чтобы использовать шаблон адаптера.

Вы бы сделали это:

class FooBarAdapter : IBar {

    private readonly Foo foo;

    public FooBarAdapter(Foo foo) {

        this.foo = foo;
    }

    public void Go() {

        this.foo.Go();
    }
}

Всякий раз, когда у вас есть Foo, но требуется IBar, вы завершаете его по запросу:

public void ContrivedScenario() {

    Foo foo = GetFooFromExternalDependency();

    FooBarAdapter adapter = new FooBarAdapter( foo );

    this.NeedsIBar( adapter );
}

public void NeedsIBar(IBar bar) { ... }

Хочу отметить, что если Foo -До- IBar преобразование происходит много, вы можете использовать неявные преобразования, так что вам не нужно явно построить FooBarAdapter экземпляры, но это спорно, если это хорошая практика разработки программного обеспечения или нет:

class FooBarAdapter : IBar {

    // (same as above)

    public static implicit operator FooBarAdapter(Foo foo) {
        return new FooBarAdapter( foo );
    }
}

Таким образом вы можете сделать это:

public void ContrivedScenario() {

    Foo foo = GetFooFromExternalDependency();

    this.NeedsIBar( foo ); // the conversion from `Foo foo` to `FooBarAdapter` happens implicitly
}

Одна из причин, почему С# не поддерживает утиную настройку, состоит в том, что только потому, что интерфейс класса (в смысле ООП, а не буквальный interface) имеет те же идентификаторы, что и другие, не означает, что они совместимы. Например, Process.Kill (убивает процесс) и MegaDeathKillBot3000.Kill (убивает все человечество), вероятно, не следует использовать взаимозаменяемо... если вы действительно этого не хотите.

Ответ 2

С# не имеет "утиной печати". Для того, чтобы вы могли передавать экземпляр из одного типа в другой, они должны иметь отношение наследования или определенную настраиваемую перегрузку. В этом случае, однако, вы могли бы создать простой подкласс для Foo, как если бы Foo не были запечатаны:

class SubFoo : Foo, IBar {
    void Go()
    {
        base.Go();
    }
}

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

class ContainsFoo : IBar 
{
     ContainsFoo(Foo foo)
     {
         this.foo = foo;
     }

     void Go() 
     {
         this.foo.Go();
     }
}

Ответ 3

В дополнение к @Dai answer альтернатива использует макет фреймворка, если вы хотите сделать свой foo-bar в тестовом проекте.

var bar = Mock.Of<IBar>();
Mock.Get(bar).Setup(b => b.Go()).Callback(() => foo.Go());

MethodThatNeedsIBar(bar);

Фреймворк создает прокси типа IBar to foo, работающий как адаптер, но проще настроить и уменьшить код.

Ответ 4

Существует также Impromptu-Interface пакет, который обрабатывает завернутый утиный набор для вас. Это хорошо зарекомендовавшая себя структура с множеством опций, включая возможность обработки коллекций.