Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to cast to interface that isn't inherited?

Tags:

c#

Why isn't it possible to cast an instance of:

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

...to this interface:

interface IBar
{
    void Go();
}

...even though Foo has the signature of IBar?

How can I turn an instance of Foo into an IBar? Assume I have no control over Foo.

like image 552
Matt Thomas Avatar asked Sep 30 '16 02:09

Matt Thomas


2 Answers

No, C# does not support duck-typing.

The OOP-approach to this problem is to use the Adapter Pattern.

You would do this:

class FooBarAdapter : IBar {

    private readonly Foo foo;

    public FooBarAdapter(Foo foo) {

        this.foo = foo;
    }

    public void Go() {

        this.foo.Go();
    }
}

Whenever you have a Foo but need an IBar you wrap it on-demand:

public void ContrivedScenario() {

    Foo foo = GetFooFromExternalDependency();

    FooBarAdapter adapter = new FooBarAdapter( foo );

    this.NeedsIBar( adapter );
}

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

I note that if Foo-to-IBar conversion happens a lot, you can make use of implicit conversions, so you don't need to explicitly construct FooBarAdapter instances, but it is debatable if this is good software engineering practice or not:

class FooBarAdapter : IBar {

    // (same as above)

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

That way you can do this:

public void ContrivedScenario() {

    Foo foo = GetFooFromExternalDependency();

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

One reason why C# doesn't support duck-typing is because just because a class's interface (in the OOP sense, not a literal interface) shares the same identifiers as another, doesn't mean they're compatible. For example Process.Kill (kills the process) and MegaDeathKillBot3000.Kill (kills all humanity) probably shouldn't be used interchangeably... unless you really want to.

like image 126
Dai Avatar answered Oct 06 '22 19:10

Dai


In addition to @Dai's answer, an alternative is using a mock framework, if you want to do your foo-bar cast in a test project.

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

MethodThatNeedsIBar(bar);

The framework creates a proxy of type IBar to foo, working like an adapter, but it's easier to setup and less code.

like image 43
Cheng Chen Avatar answered Oct 06 '22 19:10

Cheng Chen