Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I return T in a Generic<T> method?

Tags:

c#

generics

The following code says I cannot return an object of type Certification? Why not as that is the definition of T?

public T BuildList<T>(T list, AttributeModel model) where T : Certification
{
    return new Certification("", "", "");
}

Update:

I was asked to explain why I think this should work. Fair question.

I read "where T : Certification" to mean that the method can also be read as:

public Certification BuildList(Certification list, AttributeModel model)

and so, it should require returning an object of type Certification. I'm obviously not understanding something basic about generics here - what do I have wrong?

Update 2:

I tried the following per @DStanley - this also won't compile:

public class Animal
{

    public T BuildList<T>(T list, AttributeModel model) where T : Animal
    {
        return new Animal();
    }
}
like image 919
David Thielen Avatar asked Dec 11 '25 16:12

David Thielen


2 Answers

Because the constraint says that T derives from Certification. It does not say that T is Certification.

Let's replace Certification with a different type and see if it makes more sense:

public T BuildList<T>(T list, AttributeModel model) where T : Animal
{
    return new Animal("", "", "");
}

Now what if you called Dog d = BuildList<Dog>()? The method will return an Animal, not a Dog.

The compiler knows that the method is supposed to return a T but instead it is returning a Certification, which works if T is Certification, but not if T is a type that derives from Certification. You can return a more specific type but not a less specific one:

public Dog getThing<Dog>()
{
    return new Animal(); // obviously fails - an Animal is not necessarily a Dog
    return (Dog)(new Animal()); // This will _compile_ but will fail at runtime since the Animal is not a Dog
}

public Animal getThing<Animal>()
{
    return new Dog();  // Dog is a type of Animal so this is fine.
}

Whether in your case the return type should just be Certification or if you should somehow return an object of type T is unclear from the context.

I read "where T : Certification" to mean that the method can also be read as:

   public Certification BuildList(Certification list, AttributeModel model)

this is true only if T is Certification when the method is called. Since it is generic, the method can be called for any type that inherits from Certification, so if you had

public class MyCertification : Certification;

and use that for T (meaning you call var o = BuildList<MyCertification>(...)) the method now effectively becomes:

public public MyCertification BuildList(MyCertification list, AttributeModel model) 
{
    return new Certification("", "", "");
} 

since Certification is not a MyCertification (in the same way that a generic Animal is not a Dog), this would fail.

The compiler requires that whatever is returned from the method derives from T (NOT Certification). since Certification would not derived from a type that itself derives from Certification, the compiler tells you that the return type is invalid.

like image 166
D Stanley Avatar answered Dec 14 '25 07:12

D Stanley


The compiler doesn't make T a Certification. It's a wrapper. Consider the following code:

internal class Program
{
    static void Main(string[] args)
    {
        var dog = new Dog();
        var cat = new Cat();

        var farm = new AnimalFarmGeneric<Animal>();

        farm.FeedAnimal(dog);
        farm.FeedAnimal(cat);

        var catFarm = new AnimalFarmGeneric<Cat>();
        var strayCat = catFarm.GetAnimal();

        //catFarm.FeedAnimal(strayCat); // compilation error; Animal is not Cat
        farm.FeedAnimal(strayCat); // works, strayCat is Animal
    }

    public class AnimalFarmGeneric<T> where T : Animal
    {
        public T FeedAnimal(T animal)
        {
            animal.Feed();
            return animal;
        }

        // examples for showing difference in IL
        public T GetAnimalGeneric()
        {
            return (T)new Animal(); // Fails if T is Dog or Cat
        }

        public Animal GetAnimal()
        {
            return new Animal();
        }

    }

    public class Animal
    {
        public bool IsHungry = true;

        public virtual void Feed()
        {
            IsHungry = false;
        }
    }

    public class Dog : Animal
    {
        public override void Feed()
        {
            Console.WriteLine("Fed animal dog food.");
            base.Feed();
        }
    }

    public class Cat : Animal
    {
        public override void Feed()
        {
            Console.WriteLine("Fed animal cat food");
            base.Feed();
        }
    }
}

If we look at the IL generated for GetAnimal and GetAnimalGeneric, you will see they are different return types.

GetAnimal:

.method public hidebysig instance class ScratchConsole.Program/Animal 
        GetAnimal() cil managed
{
  // Code size       11 (0xb)
  .maxstack  1
  .locals init (class ScratchConsole.Program/Animal V_0) <-- Concrete Type
  IL_0000:  nop
  IL_0001:  newobj     instance void ScratchConsole.Program/Animal::.ctor()
  IL_0006:  stloc.0
  IL_0007:  br.s       IL_0009
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method AnimalFarmGeneric`1::GetAnimal

GetAnimalGeneric:

.method public hidebysig instance !T  GetAnimalGeneric() cil managed
{
  // Code size       16 (0x10)
  .maxstack  1
  .locals init (!T V_0) <---- Generic Type
  IL_0000:  nop
  IL_0001:  newobj     instance void ScratchConsole.Program/Animal::.ctor()
  IL_0006:  unbox.any  !T <---- unboxing
  IL_000b:  stloc.0
  IL_000c:  br.s       IL_000e
  IL_000e:  ldloc.0
  IL_000f:  ret
} // end of method AnimalFarmGeneric`1::GetAnimalGeneric

The confusion for me seems to be that when you use where T : Certification it is easy to read that as T is a Certification. It's really means that T and Certification are assignment compatible. The specification goes into this further than I can.

like image 38
Mufaka Avatar answered Dec 14 '25 06:12

Mufaka