1

I'm sure this has been asked before, but I can't seem to find the solution I'm looking for, despite a lengthy SO/Google search, so hoping you can answer my question.

For brevity, let's consider I have the following objects:

public interface ISectionView { }
public class SampleSectionView : ISectionView { }
public interface ISectionService<T> where T : ISectionView { }
public class SampleSectionService : ISectionService<SampleSectionView>

Now I have the following code in my controller:

ISectionView sectionType = new SampleSectionView(); // *** NOTE THE TYPE
var service = _serviceResolver.Resolve(sectionType);

And for the purpose of this, the _serviceResolver.Resolve method looks like this:

    public ISectionService<V> Resolve<V>(V sectionViewModel)
        where V : ISectionView 
    {
        var sectionViewModelTypeName = sectionViewModel.GetType().Name;

        // Ignore this, it's some AutoFac metadata resolution
        var resolvableService = _services.FirstOrDefault(s => s.Matches(sectionViewModelTypeName));

        if (resolvableService != null)
            return resolvableService.Value as ISectionService<V>; // Upcast, as this is the actual type we want!

        return null;
    }

So, as we can see above, I created the sectionType variable in my controller as an ISectionView type, which the SampleSectionView implements. This is causing me a problem in the resolve method, as what this is actually doing (as I've checked this in the Immediate Window) is the following:

return resolvableService.Value as ISectionService<ISectionView>;

This is a problem, as my service wants to be converted to the following:

return resolvableService.Value as ISectionService<SampleSectionView>;

I know that I've passed a SampleSectionView object into this resolve method, but the casting based on the generic V type parameter is getting a bit lost.

So how can I get the cast to recognise the actual concrete type, rather than the interface type which it was created under?

For the record, I know I can do the following:

var sectionType = new SampleSectionView(); 

But this is a problem, as I need a Factory method to create me the ISectionView, so I know this is the type I'm passing into the Resolve. Yes, I can see why this is a problem, so what can be done to overcome this? I'd rather not have a switch/case statement to handle the casting, if at all possible.

Thank you

Answer

Thanks to all that commented. In the end, all I had to do was modify the ISectionService interface to the following:

public interface ISectionService<out T> where T : ISectionView { }

And this was enough.

Oxonhammer
  • 190
  • 3
  • 14
  • Can you somehow have a non-generic interface instead of or as well as the generic one? And am I right in thinking that you have the `as` because without it it complains that the return type is incorrect? – Chris Feb 01 '17 at 17:48
  • You need to call `.Resolve(...)` as the only type the compiler "knows" about when it looks at the resolve call is the type of the variable, which is an interface. It does not use the type of the underlying type, which I understand is what you want. – Lasse V. Karlsen Feb 01 '17 at 18:04
  • Chris, yes, the resolvableService.Value returns an IResolvableService. Each ISectionService also implements this interface, and it's this IResolvableService that is used for the AutoFac purpose. This is why I need to now concert this to my desired interface. – Oxonhammer Feb 01 '17 at 20:38

2 Answers2

1

Interfaces do not work this way, the only way you can get an interface IInterface<IBase> to cast to IInterface<IDerived> is if the interface is declared with an out parameter.

I suggest reading the answer in this question. Im sure it will give you the info you need.

Community
  • 1
  • 1
maraaaaaaaa
  • 7,749
  • 2
  • 22
  • 37
  • 1
    While I haven't accepted this as the answer, it was indeed part of the solution, so thank you for the link in question. – Oxonhammer Feb 02 '17 at 09:23
1

I am not sure this is the best solution but you could wrap this into a non-generic method with reflection.

class ServiceResolver
{
    public ISectionService<ISectionView> ResolveNonGeneric(ISectionView sectionViewModel)
    {
        var method = GetType()
                     .GetMethod(nameof(Resolve), BindingFlags.Public | BindingFlags.Instance)
                     .MakeGenericMethod(sectionViewModel.GetType());
        return (ISectionService<ISectionView>) method.Invoke(this, new[] { sectionViewModel });
    }

    public ISectionService<V> Resolve<V>(V sectionViewModel) where V : ISectionView 
    {
        //V is SampleSectionView 
    }
}

Thereby executing:

ISectionView sectionType = new SampleSectionView();
var service = _serviceResolver.ResolveNonGeneric(sectionType);

will execute Resolve<SampleSectionView> internally.

This will require making ISectionService<T> covariant though.

bashis
  • 1,200
  • 1
  • 16
  • 35
  • Thank you, I'll try this out. I was looking into the Visitor Pattern to handle some double-dispatching, but this looks much simpler. – Oxonhammer Feb 01 '17 at 20:34
  • Thanks bashis. I implemented this, but the key to this, as maksymiuk also stated, was to make the T parameter an out (covariant) type. – Oxonhammer Feb 02 '17 at 09:17