NOTE: All of the code below exists in this .NET Fiddle.
I'm having trouble (again) trying to grok Generics/covariance/contravariance. :(
It's so killing me - so please be kind to me.
So, I have this fictitious pet shop and I'm trying to easily search for a Pet in our pet-inventory.
The main search class SearchMagic
is where all my greif is. I'm trying to say the following:
var pet = searchMagic.SearchForAPet("Fido");
and it will .. in this case return a Dog
which is Fido.
Now the kicker is how I've implemented the class SearchMagic
. This is where it has the smarts to search for Cat
's first, then Dog
's. The problem is, I just can't seem to figure out a way to return the cat or dog instance, if one was found. It keeps complaining about Cannot implicitly convert...
I keep trying to say to the code: At the very least, I need a SearchResult<IAnimal>
returned. So if that's a concrete Cat
or Dog
, then awesome-sauce. Otherwise, null. And then for the code that calls that method and gets the returned result, the only things I can do on that result, are anything defined in SearchResult<IAnimal>
. So if my Cat
instance has some cat specific properties or methods, then bad luck, I can't call them on the result
because the result is an SearchResult<IAnimal>
and only the IAnimal
stuff is exposed.
Here's the code, with some comments INLINE to show where the error occurs...
using System;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var searchMagic = new SearchMagic();
var animal = searchMagic.SearchForAPet("Fido");
}
// ***********************************
// ** Interfaces / contracts **
// ***********************************
public interface IAnimal
{
string Name { get; set; }
}
public interface ISearchService<TAnimal> where TAnimal : IAnimal
{
SearchResult<TAnimal> SearchInventory(string query);
}
public class SearchResult<TAnimal> where TAnimal : IAnimal
{
TAnimal Pet { get; set; }
decimal Price { get; set; }
}
// ***********************************
// ** Concrete implementations **
// ***********************************
public class Cat : IAnimal
{
public string Name { get; set; }
}
public class Dog : IAnimal
{
public string Name { get; set; }
}
public class CatSearchService : ISearchService<Cat>
{
public SearchResult<Cat> SearchInventory(string query)
{
return new SearchResult<Cat>();
}
}
public class DogSearchService : ISearchService<Dog>
{
public SearchResult<Dog> SearchInventory(string query)
{
return new SearchResult<Dog>();
}
}
public class SearchMagic
{
private CatSearchService _catSearchService = new CatSearchService();
private DogSearchService _dogSearchService = new DogSearchService();
public SearchResult<IAnimal> SearchForAPet(string query)
{
var cat = _catSearchService.SearchInventory(query);
if (cat != null)
{
// ** ERROR: Cannot implicitly convert type 'Program.SearchResult<Program.Cat>' to 'Program.SearchResult<Program.IAnimal>'
return cat;
}
var dog = _dogSearchService.SearchInventory(query);
if (dog != null)
{
// ** ERROR: Cannot implicitly convert type 'Program.SearchResult<Program.Dog>' to 'Program.SearchResult<Program.IAnimal>'
return dog;
}
return null;
}
}
}
If you want your SearchForAPet
method to be able to return a "search result" that could either be a "dog search result" or a "cat search result", it needs to return a covariant interface, not a concrete type:
public interface ISearchResult<out TAnimal> where TAnimal: IAnimal
{
TAnimal Pet { get; }
decimal Price { get; }
}
public class SearchResult<TAnimal> : ISearchResult<TAnimal> where TAnimal : IAnimal
{
public TAnimal Pet { get; set; }
public decimal Price { get; set; }
}
...
public ISearchResult<IAnimal> SearchForAPet(string query)
{
...
}