4

lately I started to learn generics. I run into trouble with storing references to generic classes instances. As you can see, my class ListHandler can store references to specific type of BaseClass. I would love to register BaseClass instances by themselves, which is why I wanted to guarantee that they will use BaseParamClass by adding 'where'. Anyway - it does not compile.'This', does not know that T is actually BaseClassParam even with 'where' keyword in class. I don't know what is wrong here and I couldn't find answer anywhere. I would be grateful for tips/guides/solutions.

public class ListHandler
{
    private List<BaseClass<BaseParamClass>> list;

    public ListHandler()
    {
        list = new List<BaseClass<BaseParamClass>>();
    }

    public void Register(BaseClass<BaseParamClass> param)
    {
        list.Add(param);
    }
}

public class BaseClass<T> where T : BaseParamClass
{
    private ListHandler listHandler;

    public T Param { get; private set; }

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this); //throws error
    }
}
anonymcodo
  • 41
  • 7
  • 5
    Afaic that is because covariance is only allowed for interfaces, check here: https://stackoverflow.com/questions/6508529/c-sharp-covariant-generic-parameter – zaitsman Nov 27 '17 at 23:26
  • You also have a method `public void BaseClass` with the same name as the enclosing class. That's not allowed either. Was that meant to be the constructor? – Enigmativity Nov 27 '17 at 23:30
  • What is the error you are getting? Also no one can compile your code because no one has `BaseParamClass` since you did not include this in your question. – CodingYoshi Nov 27 '17 at 23:30
  • In addition to the other comments, I wouldn't call using a `List>` the simplest data structure in the world. I'd be interested to know more about the high level purpose of this code as there may be another approach - i.e. perhaps encapsulation and an interface. – Aron Nov 27 '17 at 23:35
  • Why does `BaseClass` contain a `ListHandler` when that contains a list of `BaseClass<>`? Seems rather recursive without a point, unless I'm missing that? – Enigmativity Nov 28 '17 at 00:59
  • @Enigmativity it was meant to be constructor. my bad, already edited. thanks ;) it contains ListHandler just to simplify connection between these 2 classes, as I said its just example – anonymcodo Nov 28 '17 at 07:28
  • @CodingYoshi my Error is 'Cannot convert BaseClass to BaseClass' – anonymcodo Nov 28 '17 at 07:30
  • @Aron BaseParamClass is just empty class, does matter in this example. – anonymcodo Nov 28 '17 at 07:30
  • @MateuszJaworski - How does it simplify the connection between the classes. It's confusing. Please explain the purpose of this code. – Enigmativity Nov 28 '17 at 10:21

4 Answers4

1

Why don't you make ListHandler generic as well?

public class ListHandler<T>
{
    private List<BaseClass<T>> list;

    public ListHandler()
    {
        list = new List<BaseClass<T>>();
    }

    public void Register(BaseClass<T> param)
    {
        list.Add(param);
    }
}

public class BaseClass<T> 
{
    private ListHandler<T> listHandler;

    public T Param { get; private set; }

    public BaseClass(ListHandler<T> listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this); 
    }
}

Also, it seems strange to me to have BaseClass<T> contain a reference to a class that has a reference to BaseClass<T> itself.

adjan
  • 13,371
  • 2
  • 31
  • 48
  • Making generic ListHandler was the first thing I thought about at the first place, but it didn't help or maby I did something wrong about it. – anonymcodo Nov 28 '17 at 07:32
1

I have another option for you.

Let's split the BaseClass<T> class into two with a non-generic base, like so:

public class BaseClass
{
    protected ListHandler listHandler;

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
    }
}

public class BaseClass<T> : BaseClass where T : BaseParamClass
{

    public T Param { get; private set; }

    public BaseClass(ListHandler listHandler)
        : base(listHandler)
    {
        listHandler.Register(this); // Compiles nicely! Yay!
    }
}

Now, the list inside ListHandler can be defined as private List<BaseClass> list;. That means there is no problem adding any BaseClass item to the list. We also can then define two methods for registering and fetching generic versions of the BaseClass<T> from the ListHandler. It would look like this:

public class ListHandler
{
    private List<BaseClass> list;

    public ListHandler()
    {
        list = new List<BaseClass>();
    }

    public void Register<T>(BaseClass<T> param) where T : BaseParamClass
    {
        list.Add(param);
    }

    public BaseClass<T> Fetch<T>() where T : BaseParamClass
    {
        return list.Select(x => x as BaseClass<T>).Where(x => x != null).FirstOrDefault();
    }
}

So, given a class public class FooParam : BaseParamClass { } I can write this code:

ListHandler listHandler = new ListHandler();
BaseClass<FooParam> baseClass = new BaseClass<FooParam>(listHandler);
BaseClass<FooParam> baseClass2 = listHandler.Fetch<FooParam>();

Console.WriteLine(object.ReferenceEquals(baseClass, baseClass2));

The result from this code is True is written to the console - which means I can successfully fetch the instance of BaseClass<FooParam> from the ListHandler.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • It does not guarantee then, that BaseClass is gonna have BaseParamClasss also I can't use Param (which I want to be at least BaseClassParam) in ListHandler, which is kind of my point. – anonymcodo Nov 28 '17 at 08:42
  • @MateuszJaworski - I think I'm still missing your point then on this. Can you explain what you're actually trying to do? (Don't explain what these classes should do, explain what the underlying purpose is.) – Enigmativity Nov 28 '17 at 10:20
  • I want to manage open popups, which are added to stack after opening. Each popup register itself in manager. I need generic class for BasePopup, which is going to have generic ViewModel and will be able to register itself in popups manager. – anonymcodo Nov 28 '17 at 15:51
  • @MateuszJaworski - And what does the registering of pop-ups achieve? – Enigmativity Nov 29 '17 at 01:56
  • being able to subscribe event on animiation show/hide finish, being able to restore stack of active popups and so on – anonymcodo Nov 29 '17 at 07:34
  • @MateuszJaworski - Then why do you need the strongly-typed generic parameter? – Enigmativity Nov 29 '17 at 08:45
1

Why your code doesn't compile

In order to fully understand why your code doesn't compile, you'll have to dive into covariance and contravariance, which is a big topic and hard to explain in an SO answer. It can be especially confusing if you've gotten to a point where inheritance polymorphism is second nature to you; the rules are just different enough to be make your head hurt.

Here is what is confusing--

You're used to doing this:

object a = new String(...);

But generics don't let you do this!

List<object> c = new List<string>();  //Compiler error

That's because those two Lists are not related the same way that object and string are related. One does not inherit from the other. Rather, they are different variants of a generic type definition. In the generic world, you can't assign one to the other. The same is true of this:

void Foo<T>() where T: BaseParamClass 
{
    BaseClass<BaseParamClass> a = new BaseClass<T>(); //Compiler error
}

In this example, T could be BaseParamClass or one of its derived types. They are not the same type. So to remain type-safe, the compiler has to disallow this assignment, and your Register call, which has the same type mismatch.

Standard ways around this

You need a covariant interface. These allow assignment from derived to base. So for example, while this is still illegal:

List<object> a = new List<string>();  //Compiler error

This is totally fine:

IEnumerable<object> e = new List<string>();  //Is OK

Because IEnumerable was declared to be covariant, like this:

interface IEnumerable<out T> 

Which means it is can be assigned in this way. It works because using out also adds a compiler constraint to the interface: it can be used to retrieve stuff...

interface IEnumerable<out T> 
{
    T Item[int index];
}

...but it cannot accept anything:

interface IEnumerable<out T> 
{
    Add(T item); //Compiler error
}

These constraints are what allow generics to provide early-bound type safety while still allowing certain forms of (non-inheritance) polymorphism.

What I'd suggest

Based on your comment, it sounds like you just need a container (a stack, apparently) that can hold references to these BaseClass<T> instances. If you are following separation of concerns, the stack doesn't need to actually do anything with the T, other than store it and retrieve it, and to allow it to register itself.

Since that is a separate concern, make a separate interface.

And in the interest of keeping things simple, maybe avoid using generics completely for this bit.

One way to do it--

Create an interface that allows access to everything the stack needs to know about an item it is containing. For example, if the stack contains popups of various kinds, you may want to expose the popup's title.

interface IStackable
{
    string Title { get; set; }
}

Now use it like this:

public class ListHandler 
{
    private readonly Dictionary<string, IStackable> list;

    public ListHandler()
    {
        list = new Dictionary<string, IStackable>();
    }

    public void Register(IStackable item)
    {
        list.Add(item.Title, item);
    }
}

public class BaseClass<T> : IStackable where T : BaseParamClass
{
    private ListHandler listHandler;

    public T Param { get; private set; }

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this);
    }

    public string Title { get; set; }
}

Unless there is some other requirement, you shouldn't need to make it any more complicated than that.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Thanks for your suggestions, I think I wanted to stick too much to my solution. After all I changed architecture a little bit and now it seems to work. Anyway... there still a long way to go for me, need to learn stuff about covariance and contravariance. I appreciate your help ;) – anonymcodo Nov 29 '17 at 08:09
0

All you really need to do is add an interface. This works:

public class BaseParamClass
{
}

public class ListHandler 
{
    private List<IBase<BaseParamClass>> list;

    public ListHandler()
    {
        list = new List<IBase<BaseParamClass>>();
    }

    public void Register(IBase<BaseParamClass> param)
    {
        list.Add(param);
    }
}

public interface IBase<T> where T : BaseParamClass
{
    T Param {get; }
}

public class BaseClass : IBase<BaseParamClass>
{
    private ListHandler listHandler;

    public BaseParamClass Param { get; private set; }

    public BaseClass(ListHandler listHandler)
    {
        this.listHandler = listHandler;
        listHandler.Register(this); 
    }
}

Working code on DotNetFiddle

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Ye well... after reading some stuff I thought about using interface, it seems to work but I wanted to know if there is any other way. I am also not sure if it's going to work if I make for example OtherClass : BaseClass and I want OtherClass to have OtherClassParam Param – anonymcodo Nov 28 '17 at 08:56
  • Even if we could make it work, you shouldn't be fighting the technology. Can you explain the rationale for these class relationships? Perhaps there is a simpler approach. – John Wu Nov 28 '17 at 10:05
  • I want to manage open popups, which are added to stack after opening. Each popup register itself in manager. I need generic class for BasePopup, which is going to have generic ViewModel and will be able to register itself in popups manager. I just can't add them to stack, it's the only problem I have. – anonymcodo Nov 28 '17 at 15:47
  • @Mateusz Added another answer for ya – John Wu Nov 29 '17 at 07:12