Question

I have a class that will be storing objects that derive from a base class and implement an interface. In this example, all the types derive from UIElement and implement my interface IDropTarget.

So in my class I have I can use generic type inference in a fantastic way to require this without restricting everything to a specific base class

public void AddDropTarget<TTarget>(TTarget target)
    where TTarget : UIElement, IDropTarget
{
    target.DoSomethingAsUIElement();
    target.DoSomethingAsIDropTraget();

    // Now I want to save it for another method
    list.Add(target);
}

public void DoStuff()
{
    foreach(var item in list)
    {
        item.MoreUIElementAndDropStuff();
    }
}

Unfortunately, there seems to be no way for me to store this TTarget restricted list. I can't restrict it to a specific type because I have multiple classes that derive from UIElement already (Rectangle, Button, Grid, etc.), and there's no way for me to make all these object derive from a base type.

My next solution is going to be storing a list of each type. I need to figure out if that overhead is worth it vs casting the objects each time I use them.

Was it helpful?

Solution

If you don't want cast you can create the common base class you need with a variant of the decorator pattern.

//our new base type
public interface IElementAndDropTarget : IDropTarget
{
    void DoSomethingAsUIElement();
    void MoreUIElementAndDropStuff();
}

// our decorator. We need to store UIElement and IDropTarget
// as different fields. The static "Make" is giving you
// the compile-time guarantee that they both refer
// to the same class
public class UIElementDecorator : IElementAndDropTarget
{
    private readonly UIElement t;
    private readonly IDropTarget dt;

    private UIElementDecorator(UIElement t, IDropTarget dt)
    {   
        this.t=t;
        this.dt=dt;
    }

    public static UIElementDecorator Make<T>(T t) where T: UIElement, IDropTarget
    {
        return new UIElementDecorator(t,t);
    }

    public void DoSomethingAsUIElement() {t.DoSomethingAsUIElement();}
    public void MoreUIElementAndDropStuff(){t.MoreUIElementAndDropStuff();}
    public void DoSomethingAsIDropTarget() {dt.DoSomethingAsIDropTarget();}
}

with this, your list can be

public List<IElementAndDropTarget> list = new List<IElementAndDropTarget>(); 

in AddDropTarget you can populate it with the decorated classes as:

list.Add(UIElementDecorator.Make(target));

and that's it. The rest of your code can stay the same.

OTHER TIPS

You can not add two different types into a list this way. You are basically saying that you want they generic type to be either X OR Y. Imagine what would happen when you did a foreach, the compiler would not know if you wanted a UIElement or a IDragDrop.

You just have to maintain type safety manually. Use a private list (like you are doing, I assume) and then do the cast manually. It is up to you which type you want to use, or any.

public class MyClass {

    private _list = new List<object>();  // don't store any type info for now

    public void AddDropTarget.... /* this is already correct */

    public void RunUiStuff() {
        // get all items that are of type UIElement.  Items of wrong type will be ignored
        foreach(var e in _list.OfType<UIElement>()) {

        }
    }

    public void RunDropStuff() {
        // cast works as well, but will throw if any object is not the correct type
        foreach(var e in _list.Cast<IDropTarget>()) {
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top