neggenbe
neggenbe

Reputation: 1885

c# generics with abstract method

I'm trying to implement some generic method for an abstract class as follows :

public abstract class MyAbstractClass : MyBaseObject {
    public MyAbstractClass() : base() { } // there is a parameterless constructor...
}

public class MyList<T> where T : MyBaseObject, new() {
    // a generic container that is designed for the base class
}

//--- some paint control of mine
public class PaintControl : IDisposable {
    public void InitDrawItems(MyList<MyAbstractClass> items) {
        // paint items => this is where the compilation error occurs...
    } 
}

I get the following compilation error :

Error 24 'MyAbstractClass' 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 'MyList'

Of course, I'd like to use the abstract MyAbstractClass class (which has several children to handle painting accordingly). Is there a way around this?

EDIT: I did make the class Abstract to make absolutely sure the children actually DO implement the abstract methods.

Upvotes: 2

Views: 2372

Answers (6)

Samuel Vidal
Samuel Vidal

Reputation: 943

It's very simple, you can't instantiate an abstract class and so T = MyAbstractClass clashes with the where T : new() constraint.

Upvotes: 0

Alfie
Alfie

Reputation: 2013

As @Martin pointed out in the comments, from the docs:

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.

And the code you have written to define MyList<T>:

public class MyList<T> where T : MyBaseObject, new()

says MyList<T> is a generic class where T must have the new constraint (be type that you can instantiate).

So the reason of your compilation error on the code:

public void InitDrawItems(MyList<MyAbstractClass> items)

is because T in this case is MyAbstractClass which is an abstract class which cannot be instantiated.

So you options are as follows:

  1. Make the method generic, like so:

    public void InitDrawItems<T>(MyList<T> items) where T : MyAbstractClass, new()
    {
        // paint items => this is where the compilation error occurs...
    }
    
  2. Remove the new constraint from T in MyList

  3. Make MyAbstractClass a normal class and not abstract.

Upvotes: 0

neggenbe
neggenbe

Reputation: 1885

The answer seems a bit of a workaround, but it actually makes lots of sense...

I know for sure which item of the list I am working with (I am looping through items). Since this item has been created, it is FOR SURE a non-abstract class. And hence, I can call the activator for that item, instead of new T() :

public void InitDrawItems(MyList<MyAbstractClass> items)
  foreach (var item in items) {
    Type type = o.GetType();
    // type is definitely NOT abstract, since the object was created, so it is a
    // non-abstract child class and I create the right "copy" of the item !
    MyAbstractClass newItem = Activator.CreateInstance(type) as MyAbstractClass;
    // ... do whatever I need with the item
  }
}

NOTE: as for generics, it makes sens that calling new T() isn't allowed for abstract classes. Put it that way : a container of generic abstract items contains items of a derived child class since the abstract parent class cannot be instantiated. So calling new T() would definitely lead to an issue if allowed by the compiler : which child class should it be?!?

Upvotes: 0

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131219

This is problem is caused by generic type variance. Declaring a concrete type actually creates a new type at compile time. The type parameters in a class can't be converted implicitly between types. There's no inheritance relation between MyList<MyBaseObject> and MyList<MyConcreteObject>.

Conversions are only permitted for generic interfaces or delegates.

There are two ways to fix this - use an interface instead of a concrete class, eg :

class MyList<T>:IList<T> where T : MyBaseObject, new() 
{

}

class PaintControl  {
    public void InitDrawItems<T>(IList<MyAbstractClass> items) //where T:MyAbstractClass,new()
    {
        //var anItem=new T();
    } 
}

Or make InitDrawItems itself generic:

public void InitDrawItems<T>(MyList<T> items) where T:MyAbstractClass,new()
{
    // No compilation errors here
} 

Upvotes: 1

StackLloyd
StackLloyd

Reputation: 439

new() doesn't allow abstract classes and interfaces to be used as T, because they are not instantiable. new() means the generic type must declare a public parameterless constructor eligible for instantiation of an object. So you have one option, in my opinion: remove the new() clause, if you are ok with any abstract type derived from MyBaseObject being used with your generic. As your class is indeed abstract and a child of MyBaseObject, that would work just fine.

Upvotes: 1

Joanvo
Joanvo

Reputation: 5817

You should remove the new() constraint for T type in MyList, since it's specifying that you can only use types that can be initialize - abstract ones can't.

You can check more details here: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

Upvotes: 0

Related Questions