Создайте экземпляр списка, в котором тип происходит от базового класса и интерфейса.

У меня есть класс, в котором будут храниться объекты, производные от базового класса и реализующие интерфейс. В этом примере все типы происходят от UIElement и реализуют мой интерфейс IDropTarget.

Итак, в моем классе я могу фантастически использовать вывод общего типа, чтобы требовать этого, не ограничивая все конкретным базовым классом.

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();
    }
}

К сожалению, у меня нет возможности сохранить этот ограниченный список TTarget. Я не могу ограничить его определенным типом, потому что у меня уже есть несколько классов, производных от UIElement (Rectangle, Button, Grid и т. д.), и у меня нет возможности сделать все эти объекты производными от базового типа.

Мое следующее решение будет хранить список каждого типа. Мне нужно выяснить, стоят ли эти накладные расходы того, чтобы приводить объекты каждый раз, когда я их использую.


person Ben Randall    schedule 13.12.2013    source источник
comment
Почему бы тебе не сделать list = new List<UIElement>(); ?   -  person Tony    schedule 13.12.2013
comment
В вашем случае вы можете добавить UIElement AsUIElement { get; } в свой интерфейс. Затем вы можете получить список IDropTarget и сделать что-то вроде foreach(var item in list) { item.AsUIElement.Focus(); }   -  person default.kramer    schedule 15.12.2013
comment
Тони: Я не могу использовать List‹UIElement›, потому что мне также нужен доступ к IDropTarget.   -  person Ben Randall    schedule 19.12.2013
comment
Крамер: Это интересное решение, немного похожее на решение Паоло ниже. Я изменил ответ, чтобы отметить его как лучший ответ.   -  person Ben Randall    schedule 19.12.2013


Ответы (2)


Если вам не нужен приведение, вы можете создать общий базовый класс, который вам нужен, с вариантом шаблона декоратора.

//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();}
}

с этим, ваш список может быть

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

в AddDropTarget вы можете заполнить его украшенными классами как:

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

и это все. Остальной код может остаться прежним.

person Paolo Falabella    schedule 14.12.2013
comment
Я знаю, что это работает, я просто не знаю, почему вы должны проходить через эту боль, особенно если вам нужно больше площади поверхности от UIElement/DropTarget, чем один или два метода. Он быстро становится громоздким. Просто откройте базовые свойства UIElement и DropTarget и избавьтесь от методов как в интерфейсе, так и в декораторе. Тогда можно и от интерфейса избавиться... теперь он нам не нужен. Итак, все, что у вас есть, — это простой класс, содержащий объект в двух готовых общедоступных свойствах, и статический фабричный метод для создания новых экземпляров. - person Andrew Backer; 19.12.2013
comment
@AndrewBacker достаточно честно. Если есть 10 или более методов для делегирования, то дополнительная инкапсуляция может не стоить делегирования всех методов вручную. - person Paolo Falabella; 19.12.2013

Вы не можете добавить два разных типа в список таким образом. По сути, вы говорите, что хотите, чтобы общий тип был либо X ИЛИ Y. Представьте, что произойдет, если вы сделаете foreach, компилятор не будет знать, хотите ли вы UIElement или IDragDrop.

Вам просто нужно поддерживать безопасность типов вручную. Используйте частный список (как вы, я полагаю), а затем выполните приведение вручную. Это зависит от вас, какой тип вы хотите использовать, или любой.

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>()) {
        }
    }
}
person Andrew Backer    schedule 13.12.2013
comment
Вывод универсального метода работает с методом, поэтому во время компиляции он может проверить, что параметр является производным от UIElement и реализует IDropTarget. Чтобы было ясно, мне не нужен тип, который может быть либо X, либо Y, я хочу оба, точно так же, как определение TTarget ограничено. Компилятор знает, что параметр должен быть обоими, и поэтому я могу использовать методы обоих типов для одной и той же переменной. Я подумал, что нет никакого способа получить то же самое, что и Список, но спросить стоило. И поскольку дженерики обеспечивают безопасность типов, мне все равно не нужно беспокоиться о .Cast<> броске. - person Ben Randall; 13.12.2013
comment
Конечно :) Это два разных типа, их нельзя объединить без нового базового класса. - person Andrew Backer; 19.12.2013
comment
Я думаю, вы неправильно понимаете вопрос. В моем примере выше AddDropTraget<TTarget> TTarget на самом деле является обоими типами. Это ОБА UIElement И IDropTarget. Ограничения универсального типа гарантируют это. TTarget относится к любому типу, который наследуется от UIElement и реализует IDropTarget. Так что я мог бы создать класс с именем MyDropButton : Button, IDropTarget и еще один класс с именем MyDropTextBlock : TextBlock, IDropTarget. Оба этих класса соответствуют ограничениям для TTarget, но у меня нет возможности создать List‹TTarget›, который может содержать оба из них, как это делает AddDropTarget. - person Ben Randall; 22.12.2013
comment
Точно. Извините, если я не ясно общаюсь. Вам понадобится общий базовый класс для всех ваших элементов управления (например, MyButtonBase: UIElement, IDropTarget). Вы можете ограничиться несколькими типами, но такие классы, как List, не ограничены. Если вы пишете собственный класс списка, вы можете сделать ограничение. - person Andrew Backer; 22.12.2013
comment
Правильно, если бы у меня был общий базовый класс, я бы смог сделать что-то подобное. Но я использую классы в WPF, поэтому не могу сделать общий базовый класс, который позволил бы мне решить проблему. - person Ben Randall; 18.01.2014