DavidRR
DavidRR

Reputation: 19397

Generic type whose type parameter is an abstract base class

Let's say I have a base class named Animal and two derived classes named Lion and Tiger. And I'd like to also have a generic type named AnimalList<Animal> to contain members of Animal. For example:

var list = new AnimalList<Animal>();

list.Add(new Lion());
list.Add(new Tiger());

Very straightforward so far but there's a catch...I'd like the base class Animal to be abstract. That is, I don't want to allow an instance of the base class to be created. However, making the base class abstract not surprisingly results in error CS0310 (see a complete example below).

I did come up with a solution: Make the base class non-abstract as required and simply throw an exception from the default (parameterless) constructor:

public Animal()
{
    throw new System.NotImplementedException();
}

But this makes me feel a little uneasy. Is there a better way?


Here's a complete example that illustrates the scenario that I describe above:

// public class Animal {}        // OK
public abstract class Animal {}  // 'abstract' keyword causes error 'CS0310'

public class Lion : Animal {}
public class Tiger : Animal {}

public class AnimalList<T> :
    Animal
    where T : Animal, new()
{
    public void Add(T animal) {}
}

class AnimalListDemo
{
    static public void Main(string[] args)
    {
        // The following statement causes:
        // error CS0310 - 'Animal' must be a non-abstract type with a
        // public parameterless constructor in order to use it as
        // parameter 'T' in the generic type or method 'AnimalList<T>'

        var list = new AnimalList<Animal>();

        list.Add(new Lion());
        list.Add(new Tiger());
    }
}

Upvotes: 1

Views: 5868

Answers (4)

DavidRR
DavidRR

Reputation: 19397

At the end of my question, I asked:

Is there a better way?

My conclusion from the helpful answers on this page: Yes!

In particular, in his comment to my question, @CodesInChaos said the following:

In general I'd consider the new() constraint a bit of a code smell.

This got me searching for an alternative to the new() constraint. (I do indeed have the need to create instances of the generic type parameter in a base class within my class hierarchy.)

I found what I was looking for in this answer to Create a new instance of T without the new constraint:

var instanceOfT = new T(); // Before

var instanceOfT = (T)Activator.CreateInstance(typeof(T)); // After

This allowed me to drop the few new() constraints that I had in my source code. And finally, dropping the new() constraints allowed me to set an intermediate class to abstract as I desired.

Upvotes: 2

nmclean
nmclean

Reputation: 7724

Interfaces are allowed to be combined with the new constraint:

public interface IAnimal {}

public abstract class Animal : IAnimal {} 

public class AnimalList<T> where T : IAnimal, new() {}

However, your first line will still fail:

var list = new AnimalList<Animal>();

AnimalList<Animal> (or AnimalList<IAnimal>) is not a valid type because the given T can't be constructed. Are you sure new is what you want?

Upvotes: 0

Alejandro
Alejandro

Reputation: 7813

Is it absolutely necessary to have the new() constraint on the generic parameter? If you remove it, it will compile, at the cost of not allowing creating new instances of T within AnimalList.

Reason for that is that if you request the class to have a public, parameterless constructor, it cannot be abstract, because abstract classes are meant specifically to be non-creatable, but you're requesting the class to be creatable with the new(). As those requirements contradict each other, the compiler complains.

The solution seems to be rather a hack, and will not work either, because the derived class constructor will also call the base class constructor (instead of completely override it) and still throw the exception.

Upvotes: 2

Jeroen Vannevel
Jeroen Vannevel

Reputation: 44439

Just remove the new() constraint. It requires you to provide a class that can be instantiated through new() (which is what the requirement is all about), but an abstract class obviously can't do this.

From MSDN:

The new constraint specifies that any type argument in a generic class declaration must have a public parameterless constructor. To use the new constraint, the type cannot be abstract.

Upvotes: 4

Related Questions