Home > Back-end >  Abstract type members in C#?
Abstract type members in C#?

Time:03-10

I love Scala's abstract type members, which is a great feature.

For example, consider this simple example, as entered into the Scala REPL:

scala> :paste
// Entering paste mode (ctrl-D to finish)

/** Base class for a pet food class.
 */
abstract class PetFood

/** Dog food.
 */
final class DogFood extends PetFood

/** Cat food.
 */
final class CatFood extends PetFood

/** An abstract Pet.
 */
abstract class Pet {

  /** Type of food this pet will eat.
   */
  type Food <: PetFood

  /** Eat some food.
   */
  final def eat(food: Food): Unit = println("Yum!")
}

/** A dog.
 */
final class Dog extends Pet {

  // Dogs require dog food.
  override type Food = DogFood
}

/** A cat.
 */
final class Cat extends Pet {

  // Cats require cat food.
  override type Food = CatFood
}


// Exiting paste mode, now interpreting.

defined class PetFood
defined class DogFood
defined class CatFood
defined class Pet
defined class Dog
defined class Cat

scala> val dog = new Dog
dog: Dog = Dog@2036f83

scala> dog.eat(new DogFood)
Yum!

scala> dog.eat(new CatFood)
<console>:14: error: type mismatch;
 found   : CatFood
 required: dog.Food
    (which expands to)  DogFood
       dog.eat(new CatFood)
               ^

Both Dog and Cat are still types of Pet, and can be used wherever a Pet is required, but they have specific food requirements. But the key point is that Pet is still polymorphic.

I'm trying to do something similar in C# (in version 7.2), but—so far as I can tell—it appears not to support the concept of abstract type members (or even just type members).

What I'd like to avoid doing is just making Pet generic, because that would destroy the polymorphism. For example:

public abstract class PetFood {
}

public sealed class DogFood: PetFood {
}

public sealed class CatFood: PetFood {
}

public abstract class Pet<F>
where F: PetFood {

    public void Eat(F food) => Console.WriteLine("Yum!");
}

public sealed class Dog: Pet<DogFood> {
}

public sealed class Cat: Pet<CatFood> {
}

This works in terms of ensuring that pets eat the correct type of food, but Pet is no longer polymorphic (I can't pass a Dog or a Cat instance when a Pet instance is required, because I need a generic argument for Pet instances).

Is there a way to achieve this in C#? (Or an alternative .NET language?)

(My actual use case is a little more complex than this, but I'm trying to keep the explanation simple. Suffice to say, it's not sufficient to create a non-generic base class for the hierarchy.)

CodePudding user response:

What I do in situations like this is separate out the non-generic stuff into an abstract, non-generic class, Pet, and make Pet derived from it. It doesn't really make sense to polymorphically pass in a pet to a place that intends to call the Eat method.

You could also make all methods or types that use Pet generic but that doesn't help with collections.

CodePudding user response:

I'm not 100% sure about your goal, but maybe something like this helps:

public interface IFood<T>
{
    string Brand { get; }
}

public class Pet<T>
{
    public void Eat(IFood<T> food)
    {
        Console.WriteLine(food.Brand);
    }
}

public class DogFood : IFood<Dog>
{
    public string Brand => "Dog Deli";
}

public class CatFood : IFood<Cat>
{
    public string Brand => "Lucky Cat";
}

public class Dog : Pet<Dog> {}
public class Cat : Pet<Cat> {}

(new Dog()).Eat(new DogFood());
(new Cat()).Eat(new CatFood());
// (new Cat()).Eat(new DogFood()); <-- won't compile
  • Related