简体   繁体   中英

Store generic type reference in C#

i'm porting some of my Java code to C# and i'm having trouble replicating this behavior:

***** JAVA CODE *****

public abstract class Fruit<T extends Fruit>{
   //Fruit implementation
}

This is great because i only want generic types that extend Fruit. Then i can store a reference for all of the concret fruit objects like this:

Banana banana = new Banana(); //This class extends Fruit
Strawberry strawberry = new Strawberry (); //This class extends Fruit

Fruit fruit;
fruit = banana;
//or
fruit = strawberry;

This works just fine. Now i'm trying the same in C# and the Fruit class is declared like this:

***** C# CODE *****

abstract public class Fruit<T> where T : Fruit<T> {
  //Fruit implementation
}

But in C#, i can't store a reference like this:

Fruit fruit; //This gives a compilation error!

i'm not able to store the banana and the strawberry in the same reference, i can only do this:

Fruit<Banana> fruit;
fruit = banana;
//or
Fruit<Strawberry> fruit;
fruit = strawberry;

I think i can get around it by adding an inheritance level like this:

abstract public class GenericFruit<T> where T : GenericFruit<T> {}

and then create the Fruit class equivalent

abstract public class Fruit : GenericFruit<Fruit>{}

and now extending Banana and Strawberry from Fruit like this:

public class Banana : Fruit {}
public class Strawberry : Fruit {}

and then store a Fruit reference:

Fruit fruit;
fruit = new Banana();
fruit = new Strawberry();

But this somehow feels like cheating :( Any ideas? Am i doing something wrong?

The problem you're running into is that you're trying to "forget" (or erase) some of the type information you've created. Let's make the example a little more concrete by adding a method to your base class:

public abstract class Fruit<T> where T : Fruit<T>
{
    public abstract T GetSeedless();
}

Okay, now let's look more closely at what you're trying to do. Let's assume that you could do exactly what you wanted and you had a fruit basket:

Fruit fruit = new Apple();
var seedlessFruit = fruit.GetSeedless();

Okay, what is the type of seedlessFruit ? You might be inclined to say that it's Fruit and that would be reasonable, but C# doesn't allow this. C# doesn't let you erase the generic parameter of a class. When you declared Fruit<T> you condemned all Fruit to have a generic parameter, you can't erase that.

I think you're close to a solution, but I think you have it a bit upside down. Instead of having the non-generic Fruit inherit from GenericFruit<Fruit> , you should flip it and have the generic version inherit from the non-generic one.

I also have one other recommendation and that's to make the non-generic Fruit into an interface rather than an abstract class. I'll demonstrate why (ultimately it's because C# doesn't allow return type covariance when overriding methods; a bummer to be sure).

public interface IFruit
{
    IFruit GetSeedless();
}

public abstract class Fruit<T> : IFruit where T : Fruit<T>
{
    public abstract T GetSeedless();

    IFruit IFruit.GetSeedless()
    {
        return GetSeedless();
    }
}

What I've done here is sort of created a fake return type covariance by explicitly implementing the IFruit interface in the Fruit class. This now allows you to store different kinds of fruits in the same reference and still use the GetSeedless method:

IFruit fruit = new Apple();
var seedlessFruit = fruit.GetSeedless();

This also allows you to pick and choose which methods and properties should be available when you want to erase the generic information. Each of those methods can be implemented explicitly in the base class and "replaced" with generic versions. That way if you do have the generic type information, you can work with more specific types.

First of all, this:

abstract public class Fruit<T> where T : Fruit<T>

Just can't work, because you are creating an infinite loop by saying T is Fruit<T> . (Start to replace T in Fruit<T> by Fruit<T> , you will see it's impossible to end).

EDIT: As Kyle said, this works.

The solution could be:

abstract public class Fruit
{
    // Generic implementation
}

abstract public class Fruit<T> : Fruit
    where T : Fruit // edit: or Fruit<T>
{
    // Overriding generic implementation
}

And you could have:

public class Banana : Fruit<YourType> // edit: or Fruit<Banana>
{
    // Specific implementation
}

Finally, this should work nicely:

Fruit fruit;
fruit = new Banana();
fruit = new Strawberry();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM