0

I have a situation where I want to create an instance of a class depending on the implementation of the interface passed in as a parameter.

public interface IFooParameters
{
    int Id { get; }
}

public interface IFooParametersAdditional
{
    int AnotherProperty { get; }
}

public class FooBarParameters : IFooParameters
{
    public int Id { get; set; } = 1;
    public int Bar { get; set; } = 42;
}

public class FooBazParameters : FooBarParameters, IFooParametersAdditional
{
    public int Baz { get; set; } = 55;
    public int AnotherProperty { get; set; } = 99;
}

public interface IFoo<in TFooParameters>
    where TFooParameters : IFooParameters
{
    void DoStuff(TFooParameters parameters);
}

public class Bar : IFoo<FooBarParameters>
{
    public void DoStuff(FooBarParameters parameters)
    {
        throw new System.NotImplementedException();
    }
}

public class Baz : IFoo<FooBazParameters>
{
    public void DoStuff(FooBazParameters parameters)
    {
        throw new System.NotImplementedException();
    }
}

public class Another : IFoo<FooBazParameters>
{
    public void DoStuff(FooBazParameters parameters)
    {
        throw new System.NotImplementedException();
    }
}

public interface IFooFactory
{
    IFoo<IFooParameters> GetInstance(IFooParameters parameters);
}

public class FooFactory : IFooFactory
{
    public IFoo<IFooParameters> GetInstance(IFooParameters parameters)
    {
        if (parameters.Id == 1)
            return new Bar(); // *** compiler error

        if (parameters.Id == 2)
            return new Baz(); // *** compiler error

        return new Another(); // *** compiler error
    }
}

// *** compiler error is:

Error CS0266 Cannot implicitly convert type 'Bar' to 'IFoo<IFooParameters>'. An explicit conversion exists (are you missing a cast?)

I'm not sure why I get this, as I thought derived implementations should (I would have thought) implicitly convert to their bases as Bar : IFoo<FooBarParameters>, and FooBarParameters : IFooParameters and this works (not bringing in generics):

public interface IBar { }
public class BarBar : IBar { }

public class Test
{
    public IBar Thing()
    {
        return new BarBar();
    }
}

The basic idea behind what I'm hoping to accomplish is, there are two potential implementations of IFooParameters to pass into the FooFactory, depending on the type and some validation, return an instance of IFoo<IFooParameter> - I only care about returning the interface, as the underlying API is the same between all implementations of IFoo<IFooParameters, though one implementation requires an additional property through FooBazParameters.

I'm pretty new to generics, so I'm not sure if what I'm attempting to do is just completely wrong, or if I'm just missing something in the implementation. But any help and/or explanation to what's going on is appreciated.

Fiddle: https://dotnetfiddle.net/wjyLS7

Slightly changed with cast: https://dotnetfiddle.net/ZFQjCn

Kritner
  • 13,557
  • 10
  • 46
  • 72
  • 1
    I don't get an error when I compile your first block of code. You need to post a proper repro. – Matthew Watson Aug 25 '17 at 13:45
  • @MatthewWatson Just copy-pasting the code gives me the same errors, as I would expect, given the code (there's no way it could possibly be allowed to compile). – Servy Aug 25 '17 at 13:46
  • @MatthewWatson yeah I'm not sure how you're getting a successful build, even the first fiddle I attached has a compiler error. – Kritner Aug 25 '17 at 14:17
  • @Servy Ah... I didn't select enough of the code when I was copy-pasting it. – Matthew Watson Aug 25 '17 at 14:19

1 Answers1

3

If the compiler allowed you to convert a Bar instance to IFoo<IFooParameters> then you would be able to call DoStuff on that returned value passing in a FooBazParameters instance as the parameter, but the Bar instance is only allowed to take in FooBarParameters instances. The only way to prevent you from being able to use the Bar instance in an invalid way is to prevent that conversion from being valid.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • ah, that makes sense! Is there any way to accomplish what I'm attempting to do? (asside from drop the factory and just have the client new up the `IFoo<...>` impls directly?) – Kritner Aug 25 '17 at 13:50
  • 1
    @Kritner Given that it never makes sense for you to treat different implementations of the interface differently, it doesn't even make sense for you to have an interface in the first place (either that or you should redesign your program so that you *can* treat the instances of the interface interchangably). – Servy Aug 25 '17 at 13:51
  • Yeah seems that'll have to be my route. At least I now understand ***why*** it doesn't work. Thanks! – Kritner Aug 25 '17 at 14:22
  • @Kritner If you want more formal explanation, you are basically trying to make your generic type parameter both covariant (`out`) and contravariant (`in`), which is basically impossible. You can take a look here for more details: https://stackoverflow.com/a/2835318/672018 – Andrzej Gis Aug 25 '17 at 14:23