I have a type Shelter that needs to be covariant, so that an override in another class* can return a Shelter<Cat>
where a Shelter<Animal>
is expected. Since classes cannot be co- or contravariant in C#, I added an interface:
public interface IShelter<out AnimalType>
{
AnimalType Contents { get; }
}
However, there is a place where an IShelter (compile-time type) is assigned a new animal, where we know for sure that the animal being set is going to be a Cat. At first, I thought I could just add a set to the Contents property and do:
IShelter<Cat> shelter = new Shelter(new Cat());
shelter.Contents = new Cat();
But adding the setter is not possible;
Error CS1961 Invalid variance: The type parameter 'AnimalType' must be invariantly valid on 'IShelter<AnimalType>.Contents'. 'AnimalType' is covariant.
This makes sense, because otherwise I could pass the catshelter to this function:
private static void UseShelter(IShelter<Animal> s)
{
s.Contents = new Lion();
}
However, I'm not going to do that. It would be nice to have some way to mark the setter as invariant so that the UseShelter function would only be able to assign an Animal, and so that this would be enforced at compile-time. The reason I need this is because there is a place in my code that knows it has a Shelter<Cat>
, and needs to re-assign the Contents property to a new Cat.
The workaround I found so far is to add a jucky runtime type check in an explicit set function; Juck!
public void SetContents(object newContents)
{
if (newContents.GetType() != typeof(AnimalType))
{
throw new InvalidOperationException("SetContents must be given the correct AnimalType");
}
Contents = (AnimalType)newContents;
}
The parameter needs to be of type object, so that this function can be specified in the interface. Is there any way to enforce this at compile-time?
* To clarify, there is a function: public virtual IShelter<Animal> GetAnimalShelter()
that is overridden and returns an IShelter<Cat>
:
public override IShelter<Animal> GetAnimalShelter(){
return new Shelter<Cat>(new Cat());
}
A minimal working example including most of the code above follows:
class Animal { }
class Cat : Animal { }
class Lion : Animal { }
public interface IShelter<out AnimalType>
{
AnimalType Contents { get; }
void SetContents(object newContents);
}
class Shelter<AnimalType> : IShelter<AnimalType>
{
public Shelter(AnimalType animal)
{
}
public void SetContents(object newContents)
{
if (newContents.GetType() != typeof(AnimalType))
{
throw new InvalidOperationException("SetContents must be given the correct AnimalType");
}
Contents = (AnimalType)newContents;
}
public AnimalType Contents { get; set; }
}
class Usage
{
public static void Main()
{
IShelter<Cat> catshelter = new Shelter<Cat>(new Cat());
catshelter.SetContents(new Cat());
catshelter.SetContents(new Lion()); // should be disallowed by the compiler
}
}