Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# generic wildcard or how to hold a reference to unknown generic inheritance

OK, so here is the situation. I've got a FlexCollection<T> class, which purpose is to hold a list of some specialization of FlexItem, therefore:

public class FlexCollection<T> where T : FlexItem, new()
{
    public void Add(T item) { ... }
    ...
}

FlexItem is not generic class itself. What I wanted to achieve is ability to hold in FlexItem's field a reference to the collection that contains the object. Unfortunately in C# it is not possible to hold reference to "any" specialization of template class (as in Java). At first I tried to use non-generic interface IFlexCollection but it actually forced me to implement each method twice, i.e.:

public class FlexCollection<T> : IFlexCollection where T : FlexItem, new()
{
    public void Add(T item) { ... } // to use in generic calls
    public void Add(object item) { ... } // to use for calls from within FlexItem
    ...
}

Then I had found out that I could make FlexItem a generic class itself! Then a specialization can hold a reference to collection of objects of this specialization (which is quite logical). Therefore:

public class FlexItem<T> where T : FlexItem<T>, new()
{
    public FlexCollection<T> ReferenceToParentCollection;
    ...
}

public class FlexCollection<T> where T : FlexItem<T>, new()
{
    public void Add(T item) { ... } 
    ...
}

Now i can declare some FlexItem<T> specialization and corresponding collection:

public class BasicItem : FlexItem<BasicItem> { public int A; }
public class BasicCollection : FlexCollection<BasicItem> { };

The problem arises when I try to extend those classes to hold additional fields. I.e. I wanted an ExtendedItem class which holds field B in addition to field A:

public class ExtendedItem : BasicItem { public int B; }
public class ExtendedCollection : FlexCollection<ExtendedItem> { };

And the thing is that ExtendedItem is a subclass of FlexItem<BasicItem> and not FlexItem<ExtendedItem>. Therefore is is impossible to declare ExtendedCollection as above. This causes a compilation error:

The type 'Demo.ExtendedItem' must be convertible to
'Demo.FlexItem<Demo.ExtendedItem>' in order to use it as parameter 'T'
in the generic type or method 'Demo.BasicCollection<T>'

Is there any way to avoid such type collision?

like image 543
Kuba Wyrostek Avatar asked Jun 27 '12 21:06

Kuba Wyrostek


2 Answers

OK, i've decided to give C# events a try. This way I actually do not need FlexItem to hold a reference to owning FlexCollection. I simply raise an event i.e. "IAmBeingRemoved" and FlexCollection's code if performing corresponding actions. Code logic is even better encapsulated then. I just hope I will not run into any performance issues or other generic-based suprises. :-) I am posting it so that maybe somebody finds it a good solution for their own problem.

like image 137
Kuba Wyrostek Avatar answered Nov 11 '22 02:11

Kuba Wyrostek


There are 2 ways you can resolve this:

You can use the base Polymorphic Collection and not inherit the base class collection:

class Program
{
    static void Main(string[] args)
    {
        BasicCollection extendedCollection = new BasicCollection();
        extendedCollection.Add(new ExtendedItem { A = 1, B = 2});
        extendedCollection.Add(new BasicItem { A = 3 });
        extendedCollection.Add(new ExtendedItem { A = 4, B = 5});

        foreach (BasicItem item in extendedCollection)
        {
            switch(item.GetType().Name)
            {
                case "BasicItem":
                    Console.Out.WriteLine(string.Format("Found BasicItem: A={0}", item.A));
                    break;
                case "ExtendedItem":
                    ExtendedItem extendedItem = item as ExtendedItem;
                    Console.Out.WriteLine(string.Format("Found ExtendedItem: A={0} B={1}", extendedItem.A, extendedItem.B));
                    break;
            }
        }
    }
}

public class FlexItem<T> where T : FlexItem<T>, new()
{
    public FlexCollection<BasicItem> ReferenceToParentCollection;
}

public class FlexCollection<T> where T : FlexItem<T>, new()
{
    public void Add(T item) { } 
}
public class BasicItem : FlexItem<BasicItem> { public int A; }
public class ExtendedItem : BasicItem { public int B; }
public class BasicCollection : FlexCollection<BasicItem>
{
    Collection<BasicItem> items = new Collection<BasicItem>();
    public void Add(BasicItem item)
    {
        item.ReferenceToParentCollection = this;
        items.Add(item);
    }
    public void Remove(BasicItem item)
    {
        item.ReferenceToParentCollection = null;
        items.Remove(item);
    }
    public IEnumerator GetEnumerator()
    {
        return items.GetEnumerator();
    }

}

Or you can box the collection class reference and unbox it when you need it since you know the type of the child object?

class Program
{
    static void Main(string[] args)
    {
        ExtendedCollection extendedCollection = new ExtendedCollection();
        extendedCollection.Add(new ExtendedItem { A = 1, B = 2, ReferenceToParentCollection = extendedCollection });
        extendedCollection.Add(new ExtendedItem { A = 3, B = 3, ReferenceToParentCollection = extendedCollection });
        foreach (ExtendedItem item in extendedCollection)
        {
            (item.ReferenceToParentCollection as ExtendedCollection) ...
        }
    }
}


public class FlexItem<T> where T : FlexItem<T>, new()
{
    public object ReferenceToParentCollection;
}

public class FlexCollection<T> where T : FlexItem<T>, new()
{
    public void Add(T item) { } 
}

public class BasicItem : FlexItem<BasicItem> { public int A; }
public class BasicCollection : FlexCollection<BasicItem> { };
public class ExtendedItem : BasicItem { public int B; }
public class ExtendedCollection : FlexCollection<ExtendedItem> { };
like image 1
Ryan Christensen Avatar answered Nov 11 '22 01:11

Ryan Christensen