Need Help With Generics

Hi Guys,

I’m trying to get Unity to let me use generics. It might not even be Unity, it may be a C# problem.

I have an abstract container class with generic contents, here:

public abstract class ComponentContainer<T> : ScenarioComponent where T : IContainable

I have a ChemicalContainer, which inherits from this class, here:

[Serializable]
    public class ChemicalContainer : ComponentContainer<Chemical>

I then have a buttonlist which usually takes a generic class and uses that to display a dynamic list of buttons, here:

[Serializable]
    public abstract class ButtonList<T>
    [Serializable] public class ContainerButtons : ButtonList<ChemicalContainer> { }
    [Serializable] public class FailureModeButtons : ButtonList<FailureMode> { }
    [Serializable] public class ChemicalButtons : ButtonList<Chemical> { }

So far so good. Using this system works perfectly, and displays all the buttons dynamically. Completely fully functional.

You see there that the ButtonList is for a concrete content type? I want that to display all the generic types inheriting from the IContainable interface.

When I try, by doing this:

[Serializable] public class ContainerButtons : ButtonList<ComponentContainer<IContainable>> { }

and then try to implement that in the main menu (which displays all the buttons), it throws up a bunch of errors:

I don’t know how to cast as the ChemicalContainer’s parent (ComponentContainer). Can anyone help?

Thanks!

I figured it out, after a few hours. For anyone who needs to know!

Casting it via LINQ will work:

AddButtons(chemicalContainerButtons, scenarioComponents.chemicalContainers.Cast<ComponentContainer<IContainable>>().ToList(), null);

Okay scratch that. This fails as well.

                // Add the two lists together to make a full list of all container types.
                List<ComponentContainer<IContainable>> containers = new List<ComponentContainer<IContainable>>();
                containers.AddRange(scenarioComponents.chemicalContainers.Cast<ComponentContainer<IContainable>>().ToArray());
                containers.AddRange(scenarioComponents.radiationContainers.Cast<ComponentContainer<IContainable>>().ToArray());

                // Add chemical container buttons.
                AddButtons(chemicalContainerButtons, containers, null);

Which is really confusing for me. Because the ChemicalContainer’s parent IS the class I’m trying to cast to, and normally casting to a parent is the most simple thing in the world. So what on earth is going on? Any ideas guys?

    [Serializable]
    public class ChemicalContainer : ComponentContainer<Chemical>
        [JsonProperty("chemical_containers")] public List<ChemicalContainer> chemicalContainers;
        [JsonProperty("radiation_containers")] public List<RadiationContainer> radiationContainers;

Continuing with my inner monologue. I’ve found this answer to why it’s going wrong: c# - Generics and casting - cannot cast inherited class to base class - Stack Overflow.

In short you can’t cast back to abstract generic classes. C# does not allow that. The closest you can is to create a generic interface, as C# 4.0 can cast out from that. They gave an awesome example I’ll post here real quick:

Whenever someone asks this question, I try to take their example and translate it to something using more well-known classes that is obviously illegal (this is what Jon Skeet has done in his answer; but I’m taking it a step further by performing this translation).
Let’s replace MyEntityRepository with MyStringList, like this:
class MyStringList : List { }
Now, you seem to want MyEntityRepository to be castable to RepositoryBase, the reasoning being that this ought to be possible since MyEntity derives from EntityBase.
But string derives from object, doesn’t it? So by this logic we should be able to cast a MyStringList to a List.
Let’s see what can happen if we allow that…
var strings = new MyStringList();
strings.Add(“Hello”);
strings.Add(“Goodbye”);
var objects = (List)strings;
objects.Add(new Random());
foreach (string s in strings)
{
Console.WriteLine(“Length of string: {0}”, s.Length);
}
Uh-oh. Suddenly we’re enumerating over a List and we come upon a Random object. That’s not good.
Hopefully this makes the issue a bit easier to understand.

Hi Nigey, You will want to do some reading on covariance and contravariance. Here is my crash course:

Theory
Generics are an implementation of a concept called “type variance”. Variance on a type is a little bit like inheritance on a type but works at a more abstract level. While a class which inherits from a type might add functionality and data, a variant of a type can add whole new semantic meanings in addition to adding functionality or data. Variants can also remove semantic meanings and functionality so I think it’s more natural to think of it as just being a different entity entirely. However, there is some value in understanding that variants are closely related to their variant type.

I feel like that explanation was very abstract, so consider an example: List is a variant of int because it varies the integer class to behave differently. In this case it adds multiple cardinality, adding the ability to discuss set operations but removing the ability to discuss identity operations. You might think of List as a very different thing than int, but they are highly related.

If you want to know more about the theory, I recommend reading about Monads.

A little more practical
Let’s go deeper. There are four main types of variants:

  • invariant
  • covariant
  • contravariant
  • bivariant

Note Notice the name of category one. This is a weird overloading of the word invariant because it is actually a subtype of a variant rather than the opposite of a variant. I think this is why the word generic has become popularized over variant.

This categorization is based on the ordering of the variants type’s subtypes, that is to say: IVariant is categorized based on the subtyping of T.

By default in C#, your variants are considered invariant. That means you cannot change the “level” of the variant type, as you discovered. In other words, a List variable cannot hold a List.

Since C# 4, some interfaces are defined as covariant. This allows an upwards (more generic) subtyping of variant types. IEnumerable is defined as such, so IEnumerable can hold an IEnumerable.

Also since C# 4, some interfaces are defined as contravariant. This allows downwards (more specific) subtyping of variant types. Action is defined as such, so Action can hold an Action.

Bivariants allow both upwards and downwards subtyping of their variant types. I don’t think bivariants can exist in the current C# syntax.

Actually practical
Let’s dig into the why – consider this simple example.

public interface IPetStore<T> : where T : Animal
{
  T GetPet(string name);
}

This interface is an example of a source because it only returns objects of its variant type. Because they do not accept instances of the variant type, sources have a special characteristic: the variant type will always fall into situations where it’s okay to refer to a more generic type. As a result, this kind of variant can be considered covariant, however C# does not consider it to be implicitly. You need to use the out keyword so the compiler knows you would like to treat this variant as covariant:

public interface IPetStore<out T> : where T : Animal
{
  T GetPet(string name);
}

The compiler will now treat IPetStore as covariant and will allow the following:

IPetStore<Animal> store = new PetStore<Dog>();

When store.GetPet() is called, as you would expect, the concrete PetStore will always return a Dog. However, since the store is covariant, its okay to assume the item will be an animal.

Unfortunately, declaring the variant as covariant applies extra restrictions on the variant: it must remain a source. In other words, it can return instances of it’s variant type but not accept them as parameters.

Let’s move on to another example.

public interface IPound<T> where T : Animal
{
  void LockUp(T animal);
}

This interface is an example of a sink because it only accepts objects of its variant type. Because they do not return instances of the variant type, sinks have a special characteristic: the variant type will always fall into situations where it’s okay to refer to a more specific type. As a result, this kind of variant can be considered contravariant, however C# does not consider it to be implicitly. You need to use the in keyword so the compiler knows you would like to treat this variant as contravariant:

public interface IPound<in T> where T : Animal
{
  void LockUp(T animal);
}

The compiler will now treat IPound as contravariant and will allow the following:

IPound<Dog> pound = new Pound<Animal>();

Because of the casting, pound.LockUp() will only accept dogs and the concrete Pound will have no trouble accommodating it.

As you might have guessed, declaring the variant as contravariant also applies extra restrictions on the variant: it must remain a sink. In other words, it can accept instances of its variant type as parameters but not return them.

1 Like

I thought I’d continue a little more, demonstrating the trouble many interfaces fall into when it comes to variance…

As a logical step in my previous examples, let’s create a new interface in the style of many of the built-in variants.

public interface ISPCA<T> where T : Animal
{
  string Surrender(T pet);
  T Adopt(string name);
}

Now that you understand variance, you should spot right away the trap I’ve walked into. This variant cannot be covariant or contravariant because it both returns and accepts instances of its variant type. As I said, many of the .net built-in variants, especially the collections have this problem.

This is a great opportunity to exercise our knowledge of the SOLID principles: “I is for interface segregation”. Splitting our variant into two pieces can help us retain our ability to have covariance and contravariance.

Let’s split.

public interface IProtectionService<in T> where T : Animal
{
  string Surrender(T pet);
}

public interface IAdoptionService<out T> where T : Animal
{
  T Adopt(string name);
}

public interface ISPCA<T> : IProtectionService<T>, IAdoptionService<T> where T : Animal
{
}

To the untrained eye, this seems needlessly verbose but it has bought us something: we can now create a single implementation of SPCA but still refer to either the source or sink services respectively based on the requirements of our code.

Consider the following type hierarchy (from specific to generic)

Cat : Pet : Animal
Dog : Pet : Animal

When type safety is required, an SPCA can be cast as an IProtectionService because it implements IProtectionService which is contravariant. The cast also makes good logical sense since any SPCA which manages pets should be able to handle receiving a household cat.

When we need to be generic, an SPCA can also be cast as an IAdoptionService because it implements IAdoptionService which is covariant. This cast also makes sense since any SPCA which manages pets will only have animals to provide and not, say, a truck.

1 Like

Besides all the attempts you’ve taken and the valuable information given by @eisenpony , looking at the second screenshot it says clearly that you try to assign / cast a List to a ComponentContainer. No matter what T is, as long as ComponentContainer has no base in common with List, which it doesn’t (at least it’s not in the snippets) you won’t get anywhere, not even with any kind of variance.

Next, what you’re actually attempting to do should work if you take @eisenpony 's information into account. From the code you’ve posted, it seems you likely need covariance. This at least requires .NET 4.

Anyway, I think there’s a more serious issue that can be identified, and that’s attempting to turn everything into generics. I can remember I tried something similar long time ago when I got in touch with generics. I attempted to use them to have a consistent API that I would only need to throw in T and everything would be fine. However, that quickly turned into a huge mess, and it’s not always maintainable.

So, while there’d be the benefit of having everything declared in a generic way, that’s often the only thing you’ll really get away with. At some point, you’ll always be forced to use a more specific type, for example when you want to make use of anything that involves the actual T.

The other problem arises from the perspective of software design. Subtyping a button for everything that has to be displayed is not a great deal. The number of button classes will grow quickly, so does the number of specific ButtonLists in your particular case) and everything that’s similarly designed.

Instead, let a button be a button, and try to solve the problem in a different way. Remove the responsibility of the button’s displaying part (or whatever needs to be done there) and move it somewhere else.

1 Like

A huge thank you to @eisenpony . I had heard the words covariance and contravariant before, but didn’t have much more of an understanding of generics than it’s basic use. This has helped. It still leaves me relatively green in WHEN to use generics and when not to, so I guess just practice practice practice with a critical eye.

@Suddoha I think it’s difficult to admit your lacks when it comes to code, but I do need to admit I’m pretty fresh and over-excited about generics. I think we all get that way when you learn a new trick. That said, I’m practising finding the best time to apply it, and what fits best.

You guys have been so helpful. I had written a whole deep explanation of what code I’m writing, and my overall architecture. I chose not to post it as it would be a lot for you guys to read, and I don’t think the client wants me sharing code. That said, what I’ve done in short is instead of making it a generic, I’ve changed the these classes to implement, and accept interfaces. So there’s a more concrete contract. It’s all working now :). I do still have one or two generics, but I would say probably only about 5% of my code base in using generics. So I think that’s mostly healthy now lol.

Thanks guys!